Compare commits

..

59 Commits

Author SHA1 Message Date
David Kohler
7468fc2385 Merge pull request #52 from kohlerpop1/fixes-updates
Changed ? to & as TikTok now sends urls with defined parameters already
2024-02-09 22:36:08 -05:00
kohlerpop1
abbb557881 Changed ? to & as TikTok now sends urls with defined parameters already 2024-02-09 22:28:34 -05:00
Jacek W
19c513afe6 Merge pull request #50 from kohlerpop1/fixes-updates
Fixed bug, added final, removed not needed initialization, and minor improvements.
2024-01-22 23:26:43 +01:00
kohlerpop1
283024a1d4 Fixed NPE - Caused by: java.lang.NullPointerException: Cannot invoke "java.lang.Thread.interrupt()" because "this.liveDownloadThread" is null
Moved websocketClient.setSocketFactory call up where it only needs called once not looped. Also added pingingTask.run for not used proxy connections.
2024-01-21 13:00:49 -05:00
kohlerpop1
a0ac9e6d79 Fixed bug, added final, removed not needed initialization, and minor improvements. 2024-01-19 16:45:35 -05:00
Jacek W
2385d1e75e Update README.md 2024-01-19 19:35:24 +01:00
Jacek W
c9c7f62d4a Update README.md 2024-01-19 19:34:39 +01:00
Jacek W
35ef95096d Update README.md 2024-01-19 19:32:48 +01:00
JW
46bcfd6eb8 Merge remote-tracking branch 'origin/master' 2024-01-19 19:31:36 +01:00
JW
38b66395cb Fixed poms files 2024-01-19 19:31:30 +01:00
GitHub Action
46e75dec1a Update version in pom.xml 2024-01-19 18:19:23 +00:00
JW
749cfcf4a6 Fixed poms files 2024-01-19 19:17:46 +01:00
JW
039f2b6a0b MINOR
Fixed poms files
2024-01-19 19:14:32 +01:00
Jacek W
1a1860e35d Merge pull request #46 from jwdeveloper/develop-1.0.18
MINOR 1.0.18



- fix small fixed related to proxy system
- introduce recorder extension
- introduce collector extension

Events:
 TikTokConnectingEvent - triggered as a first event, when client is initializing connection
TikTokRoomDataResponseEvent - returns informations about room in the JSON formmat


Bugs:
  - Live is getting disconnect after few minutes, to fix that, PingingLoop have been run in background
2024-01-19 19:08:32 +01:00
Jacek W
8a4248daa3 Merge pull request #49 from kohlerpop1/fixes-updates
Fixed SimpleExample name and imports!
2024-01-19 19:04:03 +01:00
Jacek W
ff5310f5bf Merge branch 'develop-1.0.18' into fixes-updates 2024-01-18 17:11:20 +01:00
JW
9ddec45740 Including Pinging Task 2024-01-18 17:03:50 +01:00
kohlerpop1
8a7b9e801b Removed not used SimpleExample imports! 2024-01-18 10:58:33 -05:00
kohlerpop1
7b4590d0a1 Fixed SimpleExample! 2024-01-18 10:51:03 -05:00
kohlerpop1
2555edd86f Moved proxy declaration to inside of try, updated websocket version to 1.5.5 from 1.5.4, and cleared up ProxyExample! 2024-01-16 22:09:56 -05:00
kohlerpop1
a805844522 Missed one print statement! 2024-01-15 20:58:40 +01:00
kohlerpop1
9da96b4417 Missed one print statement! 2024-01-15 12:32:31 -05:00
kohlerpop1
12cf9e641b Fixed stack overflow error! 2024-01-15 09:43:25 +01:00
kohlerpop1
6bfa0b7745 Fixed stack overflow error! 2024-01-14 20:46:58 -05:00
JW
cf9b882391 . 2024-01-14 23:16:35 +01:00
JW
913d473442 . 2024-01-14 22:55:05 +01:00
GitHub Action
72092bb56b Update version in pom.xml 2024-01-14 19:57:41 +00:00
kohlerpop1
b6247feb32 Final Proxy Commit! 2024-01-14 20:56:05 +01:00
kohlerpop1
0dd952a7fb Push for proxy test pt 4! 2024-01-14 20:56:05 +01:00
kohlerpop1
af4f2b4510 Push for proxy test pt 3! 2024-01-14 20:56:05 +01:00
kohlerpop1
2c12b71e99 Push for proxy test pt 2! 2024-01-14 20:56:05 +01:00
kohlerpop1
bc3386d21e Push for proxy test! 2024-01-14 20:56:05 +01:00
kohlerpop1
4801de58cb Added convenience methods back to TikTokLive and changed sing to sign where misspelled! 2024-01-14 20:56:05 +01:00
Jacek W
548a585e90 Update README.md 2024-01-06 17:55:51 +01:00
Jacek W
2667f04a1c Update README.md 2024-01-05 17:27:12 +01:00
GitHub Action
0a857594ea Update version in pom.xml 2024-01-05 16:26:35 +00:00
JW
b0593ba95c - refactor of the Http client
Changes:

Http-client settings in configure method

```
    TikTokLive.newClient("X")
                .configure(liveClientSettings ->
                {
                   var httpSetting = liveClientSettings.getHttpSettings();
                    httpSetting.setTimeout(Duration.ofSeconds(12));
                });
```

`TikTokLive.requests()` Easy and quick way of making
http request to tiktok
```
    var giftsResponse =TikTokLive.request.fetchGiftsData();
 ```

 Removed:
     TikTokLive.isLiveOnline(String hostName);
     TikTokLive.isHostNameValidAsync(String hostName);

     instead you can use
     ```
     TikTokLive.requests().fetchLiveUserData("Mike").getUserStatus()
     ```
2024-01-05 17:21:55 +01:00
JW
c23faffcde - refactor of the Http client
Changes:

Http-client settings in configure method

```
    TikTokLive.newClient("X")
                .configure(liveClientSettings ->
                {
                   var httpSetting = liveClientSettings.getHttpSettings();
                    httpSetting.setTimeout(Duration.ofSeconds(12));
                });
```

`TikTokLive.requests()` Easy and quick way of making
http request to tiktok
```
    var giftsResponse =TikTokLive.request.fetchGiftsData();
 ```

 Removed:
     TikTokLive.isLiveOnline(String hostName);
     TikTokLive.isHostNameValidAsync(String hostName);

     instead you can use
     ```
     TikTokLive.requests().fetchLiveUserData("Mike").getUserStatus()
     ```
2024-01-05 17:21:55 +01:00
JW
f7a92d5015 - refactor of the Http client
Changes:

Http-client settings in configure method

```
    TikTokLive.newClient("X")
                .configure(liveClientSettings ->
                {
                   var httpSetting = liveClientSettings.getHttpSettings();
                    httpSetting.setTimeout(Duration.ofSeconds(12));
                });
```

`TikTokLive.requests()` Easy and quick way of making
http request to tiktok
```
    var giftsResponse =TikTokLive.request.fetchGiftsData();
 ```

 Removed:
     TikTokLive.isLiveOnline(String hostName);
     TikTokLive.isHostNameValidAsync(String hostName);

     instead you can use
     ```
     TikTokLive.requests().fetchLiveUserData("Mike").getUserStatus()
     ```
2024-01-05 17:21:55 +01:00
GitHub Action
6a42da9ecb Update version in pom.xml 2024-01-05 16:21:21 +00:00
kohlerpop1
1308b86567 Made User#attributes final as its only set once and moved each constructor assignment to field declaration! 2024-01-05 17:06:50 +01:00
kohlerpop1
20ba88c0ac Removed useless lines, made Picture#toString, and optimized TikTokGiftManager#findById & TikTokGiftManager#findByName! 2024-01-05 17:06:50 +01:00
GitHub Action
77533ea4be Update version in pom.xml 2023-12-22 21:11:06 +00:00
Jacek W
3231924f8f Merge pull request #40 from kohlerpop1/kohlerpop1-fixes-updates
Optimized a few methods when calling and using the Api!
2023-12-22 18:46:56 +01:00
kohlerpop1
ea525470e2 Revert to StringBuilder and stacking stream line! 2023-12-22 12:22:50 -05:00
kohlerpop1
b0bf4ac606 Added @Getter to Badge classes and toString methods! 2023-12-22 10:32:49 -05:00
kohlerpop1
0b9f1570d0 Optimized a few methods when calling and using the Api! 2023-12-21 21:40:11 -05:00
kohlerpop1
7a4c7fecbd Merge remote-tracking branch 'origin/kohlerpop1-fixes-updates' into kohlerpop1-fixes-updates 2023-12-20 14:27:46 -05:00
GitHub Action
0ae9068858 Update version in pom.xml 2023-12-20 19:27:06 +00:00
kohlerpop1
8905958207 Merge remote-tracking branch 'origin/kohlerpop1-fixes-updates' into kohlerpop1-fixes-updates
# Conflicts:
#	Tools-EventsCollector/src/main/java/io/github/jwdeveloper/tiktok/tools/tester/mockClient/mocks/ApiServiceMock.java
2023-12-20 14:26:01 -05:00
JW
c12f3cc4dc . 2023-12-20 20:24:33 +01:00
David Kohler
7402899f52 Merge branch 'jwdeveloper:master' into kohlerpop1-fixes-updates 2023-12-20 14:19:37 -05:00
kohlerpop1
1b8b150d61 Removed not needed @Override in ApiServiceMock! 2023-12-20 14:18:55 -05:00
Jacek W
b2305b7bed Merge pull request #39 from kohlerpop1/kohlerpop1-fixes-updates
Added startTime to LiveRoomInfo to determine when the stream started!
2023-12-20 20:17:00 +01:00
kohlerpop1
7b911838a2 Added startTime to LiveRoomInfo to determine when the stream started! 2023-12-20 14:07:53 -05:00
Jacek W
e44cb71869 Update README.md 2023-12-19 21:57:24 +01:00
GitHub Action
af8c689417 Update version in pom.xml 2023-12-19 20:53:26 +00:00
Jacek W
81ac92fb33 Merge pull request #37 from jwdeveloper/develop-1-0-12
Including toUser in TikTokGiftEvent
2023-12-19 21:51:06 +01:00
Jacek W
534cb7906d Merge pull request #36 from jwdeveloper/develop-1-0-12
Develop 1 0 12
2023-12-19 21:43:07 +01:00
118 changed files with 11181 additions and 2145 deletions

View File

@@ -5,7 +5,7 @@
<parent>
<artifactId>TikTokLiveJava</artifactId>
<groupId>io.github.jwdeveloper.tiktok</groupId>
<version>1.0.11-Release</version>
<version>1.1.0-Release</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>API</artifactId>

View File

@@ -1,91 +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;
import lombok.Data;
import java.time.Duration;
import java.util.Map;
import java.util.logging.Level;
@Data
public class ClientSettings {
/**
* Timeout for Connections
*/
private Duration timeout;
/**
* ISO-Language for Client
*/
private String clientLanguage;
/**
* Whether to Retry if Connection Fails
*/
private boolean retryOnConnectionFailure;
/**
* Before retrying connect, wait for select amount of time
*/
private Duration retryConnectionTimeout;
/**
* Whether to print Logs to Console
*/
private boolean printToConsole = true;
/**
* LoggingLevel for Logs
*/
private Level logLevel;
/**
* Optional: Use it if you need to change TikTok live hostname in builder
*/
private String hostName;
/**
* Parameters used in requests to TikTok api
*/
private Map<String, Object> clientParameters;
/*
* Optional: Sometimes not every messages from chat are send to TikTokLiveJava to fix this issue you can set sessionId
* documentation how to obtain sessionId https://github.com/isaackogan/TikTok-Live-Connector#send-chat-messages
*/
private String sessionId;
/*
* Optional: By default roomID is fetched before connect to live, but you can set it manually
*
*/
private String roomId;
}

View File

@@ -0,0 +1,66 @@
/*
* 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.data.dto;
import lombok.*;
import java.net.*;
@Data
@AllArgsConstructor
public class ProxyData
{
private final String address;
private final int port;
public static ProxyData map(String string) {
if (string == null || string.isBlank())
throw new IllegalArgumentException("Provided address cannot be null or empty!");
int portIndex = string.lastIndexOf(':');
try {
String address = string.substring(0, portIndex);
int port = Integer.parseInt(string.substring(portIndex+1));
// Port validation
if (port < 0 || port > 65535)
throw new IndexOutOfBoundsException("Port out of range: "+port);
// IP Validation
InetAddress res = InetAddress.getByName(address);
return new ProxyData(address, port);
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Port must be a valid integer!");
} catch (UnknownHostException e) {
throw new IllegalArgumentException("Address must be valid IPv4, IPv6, or domain name!");
}
}
public ProxyData clone() {
return new ProxyData(address, port);
}
public InetSocketAddress toSocketAddress() {
return new InetSocketAddress(address, port);
}
}

View File

@@ -24,7 +24,7 @@ package io.github.jwdeveloper.tiktok.data.events;
import io.github.jwdeveloper.tiktok.annotations.EventMeta;
import io.github.jwdeveloper.tiktok.annotations.EventType;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokLiveClientEvent;
import lombok.Getter;
/**
* Triggered when the connection gets disconnected. In that case you can call connect() again to have a reconnect logic.
@@ -32,4 +32,12 @@ import io.github.jwdeveloper.tiktok.data.events.common.TikTokLiveClientEvent;
*/
@EventMeta(eventType = EventType.Control)
public class TikTokDisconnectedEvent extends TikTokLiveClientEvent {
}
@Getter private final String reason;
public TikTokDisconnectedEvent(String reason) {
this.reason = reason.isBlank() ? "None" : reason;
}
public TikTokDisconnectedEvent() {
this("None");
}
}

View File

@@ -0,0 +1,37 @@
/*
* 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.data.events.control;
import io.github.jwdeveloper.tiktok.annotations.EventMeta;
import io.github.jwdeveloper.tiktok.annotations.EventType;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokLiveClientEvent;
/**
* Triggered when client is connecting to live is successfully established.
*/
@EventMeta(eventType = EventType.Control)
public class TikTokConnectingEvent extends TikTokLiveClientEvent
{
}

View File

@@ -0,0 +1,38 @@
/*
* 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.data.events.http;
import io.github.jwdeveloper.tiktok.annotations.EventMeta;
import io.github.jwdeveloper.tiktok.annotations.EventType;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent;
import io.github.jwdeveloper.tiktok.data.requests.LiveData;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
@EventMeta(eventType = EventType.Debug)
public class TikTokRoomDataResponseEvent extends TikTokEvent
{
private final LiveData.Response liveData;
}

View File

@@ -28,15 +28,10 @@ import lombok.Getter;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.*;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
public class Picture {
@Getter
@@ -49,7 +44,6 @@ public class Picture {
}
public static Picture map(io.github.jwdeveloper.tiktok.messages.data.Image profilePicture) {
var index = profilePicture.getUrlListCount() - 1;
if (index < 0) {
return new Picture("");
@@ -74,12 +68,11 @@ public class Picture {
return CompletableFuture.supplyAsync(this::downloadImage);
}
private BufferedImage download(String urlString)
{
if(urlString.isEmpty())
{
private BufferedImage download(String urlString) {
if (urlString.isEmpty()) {
return null;
}
var baos = new ByteArrayOutputStream();
try (var is = new URL(urlString).openStream()) {
var byteChunk = new byte[4096];
@@ -103,4 +96,9 @@ public class Picture {
public static Picture Empty() {
return new Picture("");
}
}
@Override
public String toString() {
return "Picture{link='" + link + "', image=" + image + "}";
}
}

View File

@@ -37,6 +37,4 @@ public class Badge {
public static Badge empty() {
return new Badge();
}
}
}

View File

@@ -24,19 +24,22 @@ package io.github.jwdeveloper.tiktok.data.models.badges;
import io.github.jwdeveloper.tiktok.data.models.Picture;
import io.github.jwdeveloper.tiktok.messages.data.BadgeStruct;
import lombok.Getter;
@Getter
public class CombineBadge extends Badge {
private final Picture picture;
private final String text;
private final String subText;
public CombineBadge(BadgeStruct.CombineBadge combineBadge) {
picture = Picture.map(combineBadge.getIcon());
text = combineBadge.getText().getDefaultPattern();
subText = combineBadge.getStr();
}
}
@Override
public String toString() {
return "CombineBadge{picture=" + picture +", text='" + text + "', subText='" + subText + "'}";
}
}

View File

@@ -24,12 +24,18 @@ package io.github.jwdeveloper.tiktok.data.models.badges;
import io.github.jwdeveloper.tiktok.data.models.Picture;
import io.github.jwdeveloper.tiktok.messages.data.BadgeStruct;
import lombok.Getter;
@Getter
public class PictureBadge extends Badge {
private final Picture picture;
public PictureBadge(BadgeStruct.ImageBadge imageBadge) {
public PictureBadge(BadgeStruct.ImageBadge imageBadge) {
picture = Picture.map(imageBadge.getImage());
}
}
@Override
public String toString() {
return "PictureBadge{picture=" + picture + "}";
}
}

View File

@@ -23,12 +23,18 @@
package io.github.jwdeveloper.tiktok.data.models.badges;
import io.github.jwdeveloper.tiktok.messages.data.BadgeStruct;
import lombok.Getter;
@Getter
public class StringBadge extends Badge {
private final String text;
public String text;
public StringBadge(BadgeStruct.StringBadge stringBadge) {
this.text = stringBadge.getStr();
}
}
@Override
public String toString() {
return "StringBadge{text='" + text + "'}";
}
}

View File

@@ -23,13 +23,18 @@
package io.github.jwdeveloper.tiktok.data.models.badges;
import io.github.jwdeveloper.tiktok.messages.data.BadgeStruct;
import lombok.Getter;
public class TextBadge extends Badge
{
@Getter
public class TextBadge extends Badge {
private final String text;
public TextBadge(BadgeStruct.TextBadge textBadge)
{
public TextBadge(BadgeStruct.TextBadge textBadge) {
this.text = textBadge.getDefaultPattern();
}
}
@Override
public String toString() {
return "TextBadge{text='" + text + "'}";
}
}

View File

@@ -41,6 +41,10 @@ public enum Gift {
LOVE_CHAT(6205, "Love Chat", 400, "https://storage.streamdps.com/iblock/440/4402267722e227b72adc97db92504b75/ae0f562146e701f32ae96761ac67c5cc.png"),
SPRING_TRAIN(8152, "Spring train", 3999, "https://storage.streamdps.com/iblock/035/035862dc0952468fc95f02995cec0f22/eeb69650806ea4c2e22558ef4b5e2b47.webp"),
CHRISTMAS_MARKET_G(7377, "Christmas Market G", 2000, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/f498f29ef628c8318006a9ff2f49bf08~tplv-obj.png"),
CHICK(8165, "Chick", 10, "https://storage.streamdps.com/iblock/54e/54e5c232c74094c8e4b4d5678552f756/8132c0b012e7100540e1f1e2a5b3265d.webp"),
DOUBLE_TROUBLE(8038, "Double trouble", 2988, "https://storage.streamdps.com/iblock/a23/a23f89b59cebf6d82ba64437e0ce52c9/d13464a899047febd2bd3db61835cb1b.webp"),
@@ -49,6 +53,8 @@ public enum Gift {
FALCON_6367(6367, "Falcon", 10999, "https://storage.streamdps.com/iblock/f88/f886e7678bef35f8c762a323386e6d23/7249e0af64c78d1d569a8d7a86ab58cd.png"),
HAPPY_FRIDAY(8265, "Happy Friday", 399, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/65e8fcb76825b9ec36a24faf9a3e9495~tplv-obj.png"),
SNEAKERHEAD(7394, "Sneakerhead", 1, "https://storage.streamdps.com/iblock/f64/f648c76bae6ef273077c74cc9312b126/87f4891550b2cfd3e49973f7f87dbdb2.webp"),
PANTHER_PAWS(7204, "Panther Paws", 199, "https://storage.streamdps.com/iblock/6e0/6e097d88e5e088d0228c702456e58450/72afb8bfa2231766da6817e911702d4b.webp"),
@@ -73,6 +79,8 @@ public enum Gift {
PLAY_SAMBA(5793, "Play Samba", 99, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/fd3d6cc127464bacded6ed009074ae2f~tplv-obj.png"),
WOOLY_HAT(7458, "Wooly Hat", 199, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/a234d0187047fa48805c8ea2e1f1f756~tplv-obj.png"),
LITTLE_CROWN(6097, "Little Crown", 99, "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/cf3db11b94a975417043b53401d0afe1~tplv-obj.jpg"),
RUBY_RED(8434, "Ruby red", 88, "https://storage.streamdps.com/iblock/405/405fcf52a1de3d14ab9834c1f30cc330/0deed9ee2c79ba6bf2005b0ce667bf60.webp"),
@@ -81,6 +89,8 @@ public enum Gift {
COTTON_CANDY(7265, "Cotton Candy", 700, "https://storage.streamdps.com/iblock/51f/51f64a93c515f4a45169f24a52179f2f/730beb9631b1af4edfaf714d7686df04.webp"),
HOT(6756, "Hot", 10, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/ec679890070187b61620b9662afb814e~tplv-obj.png"),
KNIGHT_HELMET(8672, "Knight Helmet", 199, "https://storage.streamdps.com/iblock/291/2915da07301fcb6a9a4d3e515931c2c8/31ebb4cad7a264fe9657a3ddfaca4eaa.webp"),
SUB_STAR(7072, "Sub Star", 1, "https://storage.streamdps.com/iblock/98f/98fea40fc19cc9dbd9a083b0844c163b/af7dd985812299d89f6cfa49c84e7eaf.webp"),
@@ -97,6 +107,8 @@ public enum Gift {
MIKE(7789, "Mike", 4000, "https://storage.streamdps.com/iblock/de0/de0da7b6ce6ba19125b1c4eb2fd2966a/6804a72c00714de05f9239be7bd5b515.webp"),
STAY_WARM(9682, "Stay Warm", 450, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/abd104eb08ce0c351292036d8897fb8d.png~tplv-obj.png"),
HEADPHONE(6609, "Headphone", 199, "https://storage.streamdps.com/iblock/ce9/ce95ea6922db1e776296819861d69ddb/b0b11c041a28d46e51ff1ed8f288fe91.webp"),
HEADPHONE_8017(8017, "Headphone", 199, "https://storage.streamdps.com/iblock/055/05573a16af395b896b26847bc77fbb5e/55c0f27976902374940cfb54f22728d0.webp"),
@@ -105,6 +117,8 @@ public enum Gift {
ICE_CREAM_8963(8963, "Ice cream", 5, "https://storage.streamdps.com/iblock/f72/f726165be6e93bdc69724375e7931dde/2e749d8d397b3ce5e6bcc90402f27c7d.webp"),
_2024_GLASSES(9640, "2024 Glasses", 224, "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/512d2e9934cbae8d1019a391814edbd2.png~tplv-obj.jpg"),
GIVE_IT_ALL(6649, "Give It All", 1, "https://storage.streamdps.com/iblock/de8/de8468d1003361452021c2d4796bb0f6/574aa0cdd7b418a2a3af2ca4739e9e7f.webp"),
MARVELOUS_CONFETTI(7121, "Marvelous Confetti", 100, "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/fccc851d351716bc8b34ec65786c727d~tplv-obj.jpg"),
@@ -145,12 +159,16 @@ public enum Gift {
WATERMELON(8826, "Watermelon", 10, "https://storage.streamdps.com/iblock/84e/84e29ce96978961b12f1e88dd985b938/08e2a0ac2c2e2794aa2558e67d387639.webp"),
PEGASUS(9427, "Pegasus", 42999, "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/f600a2495ab5d250e7da2066484a9383.png~tplv-obj.jpg"),
SHOW_TIME(6907, "Show Time", 3999, "https://storage.streamdps.com/iblock/d72/d722e6d78821a169ff9a6d128127c696/f457c04596d723f9033842f3417b6a72.webp"),
LOVE_FOCUS(6436, "Love Focus", 199, "https://storage.streamdps.com/iblock/cd1/cd1096cb1507fe07b633dad0b0aee967/d6b2e544219ed4d3c3263d319ab9bc5f.png"),
CHICKEN_LEG(6209, "Chicken Leg", 10, "https://storage.streamdps.com/iblock/ef7/ef776169ede6c4a635cef2b3ab35d29a/7683229a73330c04463d2b97984ea114.png"),
KING_CAKE_(6112, "King Cake ", 9, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/aa99da9f6b499ff879c3860e888a53ae~tplv-obj.png"),
BEACH_DAY(6431, "Beach Day", 2999, "https://storage.streamdps.com/iblock/c2d/c2df4a57f2ab16e641630b7077b40900/ba3cf6fd32d224f0014bd395b011f7a0.webp"),
CUBE(9184, "Cube", 10, "https://storage.streamdps.com/iblock/69d/69dab4e352882c0bd29c3864e24d80de/258857221189c76260b6af5eeb43e93b.webp"),
@@ -159,10 +177,16 @@ public enum Gift {
SPEEDBOAT(5763, "Speedboat", 1888, "https://storage.streamdps.com/iblock/55f/55f832ac0d4e25f2527b2cf87ae8af08/ec99908e1787ae32c1387a20db7ca5ac.png"),
SNOWMAN(7551, "Snowman", 99, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/e094e0fafc14aaf127fa0d0a7926619a~tplv-obj.png"),
YELLOW_BUS(8263, "Yellow Bus", 6000, "https://storage.streamdps.com/iblock/88d/88df4387d65bcc77b691098fd649bd59/ad401a92ddba9aae15bb777f9f38638d.webp"),
ROSA_NEBULA(8912, "Rosa Nebula", 15000, "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/f722088231103b66875dae33f13f8719.png~tplv-obj.jpg"),
I_M_HERE(9354, "I'm here", 1, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/7006392a82d57452d5ef08dd90e169c1.png~tplv-obj.png"),
HOT_CHOCO_GDM_23(7523, "Hot Choco GDM 23", 30, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/f62f5912077d9af84256de288399125a~tplv-obj.png"),
KISS(5284, "Kiss", 150, "https://storage.streamdps.com/iblock/d3d/d3df4a0ecebd25c21e7ce5a6f910f8f1/d9ce143ac4707f74d8b1fe4708a92ab3.png"),
KISS_5481(5481, "Kiss", 150, "https://storage.streamdps.com/iblock/5cc/5cca201687ef878daf36dfe39fd26807/b2171e9cc191783679794f42246c4ceb.webp"),
@@ -181,6 +205,8 @@ public enum Gift {
COWBOY_HAT(8842, "Cowboy Hat", 199, "https://storage.streamdps.com/iblock/5f3/5f3df5eccbc82f458fdacd0f82d13e40/40980853c80e3da0e902a1db49ea9798.webp"),
TURKEY_FACE_GDDEC(9581, "Turkey Face GDDec", 399, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/65349d1ef783fc207c1d2b54a8d521a7.png~tplv-obj.png"),
FESTA_JUNINA_S_HAT(8638, "Festa Junina's Hat", 199, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/61b32ccce11b289b3c1db7438dfb4450~tplv-obj.png"),
SPORTS_CAR(6089, "Sports Car", 7000, "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/e7ce188da898772f18aaffe49a7bd7db~tplv-obj.jpg"),
@@ -191,6 +217,8 @@ public enum Gift {
CHOCO_PIE(6503, "Choco Pie", 10, "https://storage.streamdps.com/iblock/5a7/5a7610069bd417a2847f34c6c0b2821d/5faa955edd066d1140abb048f32be815.webp"),
I_M_BLUE(7707, "I'm blue", 5, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/c560ec76d5599198aaea9377c5ffab6e~tplv-obj.png"),
WHALE_DIVING(6084, "Whale diving", 1750, "https://storage.streamdps.com/iblock/5b2/5b27c388fe0d4dbe0a5f0a44ba7a8410/602a5a7cf538240f48ccf47c13237aa2.png"),
WHALE_DIVING_6820(6820, "Whale diving", 2150, "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/46fa70966d8e931497f5289060f9a794~tplv-obj.jpg"),
@@ -209,8 +237,12 @@ public enum Gift {
PEARL(5664, "Pearl", 800, "https://storage.streamdps.com/iblock/d42/d4241b9de546fb190964c12adeecabca/d03fe09dd3400422c55953555066487e.png"),
PLAY_FOR_YOU(9535, "Play for you", 299, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/182659e90a3432aa155e61c9c0d89df0.png~tplv-obj.png"),
LET_US_DANCE(7196, "Let Us Dance", 1999, "https://storage.streamdps.com/iblock/cae/caeaf097812661e65ff761aa60d5300a/444a1217ff8fbdeaf8e4682405871c7a.webp"),
_2024_COUNTDOWN(9641, "2024 Countdown", 2024, "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/a358a3ce18241dcc6e7d0b02d091d563.png~tplv-obj.jpg"),
MINI_SPEAKER(6042, "Mini Speaker", 1, "https://storage.streamdps.com/iblock/1b1/1b117cbff78bfb7f50ba4d90a16c6112/30f4b176fd30683e3cbfc9013fe96d82.png"),
FLORAL_BLOOM(5788, "Floral Bloom", 1500, "https://storage.streamdps.com/iblock/858/85827a8e5266c8d4c697d9aa930fead6/149392b39b041febde90bc4ea80ce1a5.png"),
@@ -233,10 +265,14 @@ public enum Gift {
ARCADE_GAME_7041(7041, "Arcade Game", 1200, "https://storage.streamdps.com/iblock/fd0/fd0785612b024900444a0a69083400ff/3181d6af50b05dd65a7ba75902bb5b94.webp"),
GORGEOUS_TROPHY(6741, "Gorgeous Trophy", 7000, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/279c9495c2150e333bc4bc13761d177e~tplv-obj.png"),
IT_S_CORN(6928, "It's corn", 1, "https://storage.streamdps.com/iblock/d48/d48869b41c99cf004857fb74aff97552/75f200300cdaf3424287814ec55e9656.webp"),
SUSHI_SET(7226, "Sushi Set", 20, "https://storage.streamdps.com/iblock/097/09752a51af505fbde2e9aa853d1ada62/3b981d4797111c44c45fbd8de5201fbe.webp"),
YEAH_NAH(9576, "Yeah Nah", 1, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/4b20c5aab3841657a343be3769307805.png~tplv-obj.png"),
BIRTHDAY_GLASSES(6776, "Birthday Glasses", 199, "https://storage.streamdps.com/iblock/98d/98deaf0a4a818ba6b0333ca9aee4db59/97520c1ceae957c77ef1dbcc0f092187.webp"),
SOCKS_AND_SANDALS(6618, "Socks and Sandals", 150, "https://storage.streamdps.com/iblock/da2/da28ef4030197f812686f10b2c3f06c7/7cb8ebff6f6028e2a56b2c0c268c3620.webp"),
@@ -265,6 +301,8 @@ public enum Gift {
TIKTOK_VOLCANO(6869, "TikTok Volcano", 4000, "https://storage.streamdps.com/iblock/e6d/e6d4c0d014c552ec6e8eccb804a7659f/9678f5e24b6e9b069b43c4f84a536d9f.webp"),
KITTEN_PAW(9647, "Kitten Paw", 299, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/332520d7b5085ce591396c8d2bb9d352.png~tplv-obj.png"),
_2023(7604, "2023", 1, "https://storage.streamdps.com/iblock/8fb/8fba3d5f6bf547ba8c94d3f393992d46/e3cfc1cfea30d7c139f7c4943f5d3b26.webp"),
ROMAN_EMPIRE(7166, "Roman Empire", 199, "https://storage.streamdps.com/iblock/c77/c778c4e5cd1c68a50dcc06e4bfc3aa08/48edf8b190d98b0a3cc4623e6cc9a22c.webp"),
@@ -285,6 +323,8 @@ public enum Gift {
PIZZA(7055, "Pizza", 40, "https://storage.streamdps.com/iblock/c9d/c9d7f483cc0059a1e8165bfbd1341688/307a559eb2b371b92b8ea36ae96bfa30.webp"),
CHRISTMAS_POTATO(9587, "Christmas Potato", 10, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/5448f1f5157d3a4a88e0f57acf3dbfe0.png~tplv-obj.png"),
AMUSEMENT_PARK(9466, "Amusement Park", 17000, "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/12ecc01c2984c5d85bb508e80103a3cb.png~tplv-obj.jpg"),
CLUB(6417, "Club", 2000, "https://storage.streamdps.com/iblock/49b/49be18ae5914346ffcaf15a519ba9c1c/41326cb23d22010f0c4a8edf5bd27615.webp"),
@@ -293,11 +333,15 @@ public enum Gift {
DANCING_CAPYBARAS(8806, "Dancing Capybaras", 2200, "https://storage.streamdps.com/iblock/ac2/ac2606f1dc2504c9a1b7974f40074c87/c243031480e8f2e4bbd8e7a43228ff1f.webp"),
HUSKY(7920, "Husky", 299, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/a2f5d595e9d96aec19a7c0ed5fa9b017~tplv-obj.png"),
DUCK(6265, "Duck", 299, "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/e172f660a1d4f95813a3ace0fde42323~tplv-obj.jpg"),
XMAS_MISHKA_BEAR(9617, "Xmas Mishka Bear", 199, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/700c1c8817847317407cc2b8c6c9da42.png~tplv-obj.png"),
FLOWER_OVERFLOW(6148, "Flower Overflow", 4000, "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/743c4bb44e7e0bf251a7f2f5ada231ee~tplv-obj.jpg"),
PINK_SHOES(8815, "Pink shoes", 5, "https://storage.streamdps.com/iblock/387/387c559abfc868aa8f7d605a25748c14/06e08ba736cb17076b9c314058160ad2.webp"),
PINK_SHOES(8890, "Pink Shoes", 5, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/cba8a7c718988bd51c7b6055e9ab1ec4.png~tplv-obj.png"),
PINK_SHOES_8843(8843, "Pink shoes", 5, "https://storage.streamdps.com/iblock/e32/e328784531bfcd4773983c6a8e205a44/a3b5a6f4fa914fdf10b754ee59dc34a4.webp"),
@@ -313,8 +357,14 @@ public enum Gift {
SUNDAY_ROAST(6634, "Sunday Roast", 199, "https://storage.streamdps.com/iblock/218/218658dfe16bf8eeb11824cae5788028/95bbb526ea861ef2ba3dbe020431374f.webp"),
_2024_JOYLENS(9643, "2024 JoyLens", 224, "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/36d82bbcd87c1914df84262d9bdd9b95.png~tplv-obj.jpg"),
LEDERHOSEN(6975, "Lederhosen", 10, "https://storage.streamdps.com/iblock/7c7/7c72a908dce6d9df4db0a6159be1751b/2ff181aa1fae6088a37f942d51401176.webp"),
LOVE_U(7697, "LOVE U", 899, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/79d45877691333e2ba69a9098406e95c~tplv-obj.png"),
_2024(9639, "2024", 1, "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/484a44bbe62ce47687d1da31a6602bbd.png~tplv-obj.jpg"),
MATE_TEA(7089, "Mate tea", 10, "https://storage.streamdps.com/iblock/506/506e98699cdfefd679b35ea5170823b0/a95e9e3721c9b86e3342169b3211b30e.webp"),
TEAM_BRACELET(9139, "Team Bracelet", 2, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/54cb1eeca369e5bea1b97707ca05d189.png~tplv-obj.png"),
@@ -333,12 +383,16 @@ public enum Gift {
BIGFOOT(9147, "Bigfoot", 3000, "https://storage.streamdps.com/iblock/f95/f95a4fcfa57150610fa50542db5b0990/ecb879cd751e580d3fe92770788c1735.webp"),
BOO_THE_GHOST(9304, "Boo the Ghost", 88, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/cb909c78f2412e4927ea68d6af8e048f.png~tplv-obj.png"),
SO_BEAUTIFUL(7024, "So Beautiful", 1, "https://storage.streamdps.com/iblock/ad6/ad67c8d6c93ff4c375568b0bfabbed6f/c68dfd6fda7e8bd84f0bc7fa9ce47af0.webp"),
SUNGLASSES(5509, "Sunglasses", 199, "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/08af67ab13a8053269bf539fd27f3873.png~tplv-obj.jpg"),
PANDA(37, "Panda", 5, "https://storage.streamdps.com/iblock/833/833aadcba552a8a2cc779dd8d4c537c7/f952c72ee1f40e4fcd07d713b3da6565.png"),
MISS_YOU(8803, "Miss You", 1, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/3c53396b922691a7520698f47105a753.png~tplv-obj.png"),
TIKTOK_UNIVERSE(6038, "TikTok Universe", 34999, "https://storage.streamdps.com/iblock/a79/a79204e0fab55cdc35ca0bdfa019face/8f06121e1c15be8566b3fc02982a2027.png"),
TIKTOK_UNIVERSE_6039(6039, "TikTok Universe", 34999, "https://storage.streamdps.com/iblock/49d/49d934dc15cf5efc3ebef902a5974d56/04799e79cb4bd04a20d77d2f3fa9922d.png"),
@@ -353,6 +407,8 @@ public enum Gift {
PINCH_CHEEK(6694, "Pinch Cheek", 199, "https://storage.streamdps.com/iblock/f6c/f6c95968ca266cbb2527af09989eaea0/27be132509198253b5c48e5495038e5b.png"),
HOLIDAY_STOCKING(7504, "Holiday Stocking", 5, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/e05de50999ebb446e15c4947b30d3140~tplv-obj.png"),
MARVIN_THE_MONKEY(6843, "Marvin the Monkey", 10, "https://storage.streamdps.com/iblock/31a/31a03cf8430fa062064dd9e544910de2/e7939198db3920aeaf3d95167712af0e.webp"),
ELEPHANT_TRUNK(8260, "Elephant trunk", 299, "https://storage.streamdps.com/iblock/1ea/1eafea22e99969312cda7c142d8eb3c5/59f72e0dce1bc4fcf83a34f56872b492.webp"),
@@ -369,6 +425,8 @@ public enum Gift {
ROSE(5655, "Rose", 1, "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/eba3a9bb85c33e017f3648eaf88d7189~tplv-obj.jpg"),
THE_VAN_CAT(9650, "The Van Cat", 799, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/6973dd1b6d3dee3ca3f0ebac3c1d2977.png~tplv-obj.png"),
TRENDING_FIGURE(9138, "Trending Figure", 999, "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/df7b556ccf369bf9a42fe83ec8a77acf.png~tplv-obj.jpg"),
ROSA(7997, "Rosa", 10, "https://storage.streamdps.com/iblock/486/486a2490c987c2bb97b6068fd5aac5ab/49d9045fcfe94bbfbd08c3363bb4512a.webp"),
@@ -385,12 +443,16 @@ public enum Gift {
COTTON_S_SHELL(8352, "Cotton's Shell", 5, "https://storage.streamdps.com/iblock/766/7665d59f0ef96aecd2dac6fc5b0c19a4/3b169a12b4f8686c68d596f6d47d2f77.webp"),
KIWI_BIRD(9667, "Kiwi Bird", 10, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/b73cb4aaa76a33efd881192589d65351.png~tplv-obj.png"),
FLAME_HEART(9087, "Flame heart", 1, "https://storage.streamdps.com/iblock/10d/10df10624cdeebe8ff5e0e89e8c8e960/28b8da2878a420f8465cbbc1ec1e6b58.webp"),
FAIRY_BREAD(5823, "Fairy Bread", 1, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/a42f9ac9cd6b26da03818ff65ac919f1~tplv-obj.png"),
TIKTOK_SHUTTLE(6751, "TikTok Shuttle", 20000, "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/8ef48feba8dd293a75ae9d4376fb17c9~tplv-obj.jpg"),
INDEPENDENCE_DAY(6633, "Independence Day", 10, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/b967993872a6e40f3477d30545f8d2eb~tplv-obj.png"),
MAGGIE(7911, "Maggie", 15000, "https://storage.streamdps.com/iblock/a12/a12a1b23f1f6a19d728de84e1f43e21d/ff288346e9855a9bb6deb4450491028f.webp"),
PANDA_SKYDIVING(8812, "Panda skydiving", 2000, "https://storage.streamdps.com/iblock/a29/a29903a975ce45f7b9939b510412fcee/051afc0510a7349a9ebfcde9e0fdec24.webp"),
@@ -401,6 +463,8 @@ public enum Gift {
GAMER_CYBER_MASK(7895, "Gamer Cyber Mask", 399, "https://storage.streamdps.com/iblock/383/383652cc1fd3cae9402eeae3a8f5ee1e/df8a16397bb0ed28c0e522b4cfb26500.webp"),
XMAS_IN_LONDON(9680, "Xmas in London", 20000, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/876204a6ad0b1b0e4675d9be42439183.png~tplv-obj.png"),
HAPPY_FATHER_S_DAY(8712, "Happy Father's Day", 1, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/193eba78ded4d388a0b5a7ae95943796~tplv-obj.png"),
KO(7542, "KO", 20, "https://storage.streamdps.com/iblock/e5e/e5efb63a21695a08d9647508aca3c95e/cffda8af4cc1a9f4a66eb01b11f4db85.webp"),
@@ -417,6 +481,8 @@ public enum Gift {
DON_T_DO_IT(7688, "Dont Do It", 500, "https://storage.streamdps.com/iblock/dca/dcac97e4190d46d113f4bdf2918ee173/4fae166b3f3273b9dbbc2a86bea0ec18.webp"),
BIRDS(5514, "Birds", 600, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/0911b5726d912dabbf6ee4b0383352ea.png~tplv-obj.png"),
GOLDEN_TRUMPET(8767, "Golden Trumpet", 15, "https://storage.streamdps.com/iblock/a44/a4441a11d3cb073e855088a4eff72fdb/020b0d041c38b00b730b28806dbe6cc5.webp"),
HANDS_UP(8244, "Hands Up", 499, "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/f4d906542408e6c87cf0a42f7426f0c6~tplv-obj.jpg"),
@@ -475,6 +541,8 @@ public enum Gift {
FOOTBALL_HELMET(7851, "Football Helmet", 50, "https://storage.streamdps.com/iblock/9cc/9cce61670c1a81b7954fcf3520dc15a2/b78182e9fd2ff1c6ae1256abd8e2e2bf.webp"),
NEMO(9704, "Nemo", 15, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/68fcf30cb3fb07e9546f5e7fbc2b0ac0.png~tplv-obj.png"),
SCORPIO_STAR_SIGN(7159, "Scorpio Star Sign", 9999, "https://storage.streamdps.com/iblock/c91/c91f3a3685b5c54d9e96d5f9443c4fda/50c48574ff60f328b7a50b80cd9aa4b8.webp"),
SOCCER_BALL(5852, "Soccer Ball", 39, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/e1932db6aea81bbddc4e7dc0229ac155~tplv-obj.png"),
@@ -487,8 +555,12 @@ public enum Gift {
HANGING_LIGHTS(5937, "Hanging Lights", 199, "https://storage.streamdps.com/iblock/e03/e03da22fa8c302dbf1d9439c65380549/6d9f912b5a9253f91c01ed58e3ccbe47.png"),
VACATION(8804, "Vacation", 1, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/8f46e8eef9cbd5304fb802104c2b4ef4.png~tplv-obj.png"),
FOOTY(5893, "Footy", 5, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/94f8ac5c7b6f90aba713b44ddac40bf1~tplv-obj.png"),
GRUMPY_GLASSES(7846, "Grumpy Glasses", 99, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/6f38f8ed7442f69a105788b5c0c74a38~tplv-obj.png"),
BATIK_CLOTHES(5461, "Batik Clothes", 1000, "https://storage.streamdps.com/iblock/46d/46d0f497391a934d27d9b993f444d8b2/121af719b172eed61d8a75c1b1341c9d.png"),
DANCING_QUEENS(9240, "Dancing queens", 20000, "https://storage.streamdps.com/iblock/c79/c793af446369ecef5238e73312c84ccd/464a76f3e6eaee9afc771f45a4bba9df.webp"),
@@ -581,12 +653,16 @@ public enum Gift {
BIRTHDAY_CAKE_9097(9097, "Birthday Cake", 1, "https://storage.streamdps.com/iblock/5b9/5b9eca4a99e965cb25183681a07a5276/c28f7e9c4a8e42460225ff2d12300ae7.webp"),
WITCHY_KITTY(7084, "Witchy Kitty", 30, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/dfce46f99a1206cca84f9092603e4783~tplv-obj.png"),
BANANA_LEAF_VESSEL(5991, "Banana leaf vessel", 5, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/8e635863e20cfa3651bd8a5b762ae72d~tplv-obj.png"),
ICE_LOLLY(6545, "Ice Lolly", 10, "https://storage.streamdps.com/iblock/93d/93dda2498d64aa0a29d444103a7804dd/92fd7397fffbaa8755cb233815964bbf.png"),
CONFETTI(5585, "Confetti", 100, "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/cb4e11b3834e149f08e1cdcc93870b26~tplv-obj.jpg"),
PANETTONE_GDM_23(7477, "Panettone GDM 23", 1, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/64ce2413a362442819b4551703b7b26c~tplv-obj.png"),
MATCH__MATCH_(7068, "Match! Match!", 200, "https://storage.streamdps.com/iblock/cb4/cb43e14c94694d3d3ae355bdfc517afd/494cd902b8018b35b6dc0f0016c89694.webp"),
SHINY_AIR_BALLOON(7123, "Shiny air balloon", 1000, "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/9e7ebdca64b8f90fcc284bb04ab92d24~tplv-obj.jpg"),
@@ -599,12 +675,18 @@ public enum Gift {
SUNSET_SPEEDWAY(6203, "Sunset Speedway", 10000, "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/df63eee488dc0994f6f5cb2e65f2ae49~tplv-obj.jpg"),
NEW_YEAR_JOURNEY(9645, "New Year Journey", 12024, "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/0ef9654d92354172fb9b3b364827940c.png~tplv-obj.jpg"),
DANCING_ADAM(7468, "Dancing Adam", 5000, "https://storage.streamdps.com/iblock/f5c/f5cda80a1f9853c49226a450faf26e8f/6318d17d7a2526f521123402d19a4c3e.webp"),
SCEPTRE(5300, "Sceptre", 150, "https://storage.streamdps.com/iblock/080/080d7e9dc934f98dd8cf5dce3b5075b2/a62a3963f6d2822177763b51d4328d37.png"),
SCEPTRE_7364(7364, "Sceptre", 150, "https://storage.streamdps.com/iblock/d2d/d2d1b0359f480a7db08e490364d056b2/bcb44a039dfa4d148af6cde9f233ea13.webp"),
GOBBLE_GOBBLE(9604, "Gobble Gobble", 1, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/ada9babc0b55cf005e8c8d13dfc30b42.png~tplv-obj.png"),
REINDEER(9670, "Reindeer", 299, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/4565fa0cd1dbf76463144b0d4cc50bf1.png~tplv-obj.png"),
SEAL_AND_WHALE(8381, "Seal and Whale", 34500, "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/3781e9159ff09272826d3f2216ba36ef.png~tplv-obj.jpg"),
HAPPY_PARTY(8247, "Happy Party", 6999, "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/41774a8ba83c59055e5f2946d51215b4~tplv-obj.jpg"),
@@ -615,8 +697,6 @@ public enum Gift {
CELEBRATION_TIME(6790, "Celebration Time", 6999, "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/e73e786041d8218d8e9dbbc150855f1b~tplv-obj.jpg"),
EXCLUSIVE_YACHT(9524, "Exclusive Yacht", 20000, "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/da8c85d5ae09ebf320216202e8fa015d.png~tplv-obj.jpg"),
MONTY(7742, "Monty", 1, "https://storage.streamdps.com/iblock/c70/c70e3a9404b18068056d04d5394d739a/4e0e55d9d10a7747b7caf462cd87b4b3.webp"),
PYRAMIDS(8416, "Pyramids", 15000, "https://storage.streamdps.com/iblock/988/988ffe82e8f3b235bd91dac1e31e708d/ad0365d14ba0480e5d6d60f6eb798608.webp"),
@@ -631,8 +711,12 @@ public enum Gift {
HI_JULY(6603, "Hi July", 1, "https://storage.streamdps.com/iblock/e03/e0301a9670584be92d945ff3cb889b99/0fbb7b11f916953201588b5bfbcb3f5a.png"),
SUMMER_IRIS_(6655, "Summer Iris ", 30, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/cb591f5b5729fa6e64cac57c78724981~tplv-obj.png"),
LIGHTNING_BOLT_(6652, "Lightning Bolt ", 1, "https://storage.streamdps.com/iblock/265/2655cafe6afc1fa0fca76a732bad4730/bfb4abdf65da281c7ccf0b682f3406a3.webp"),
AURORA(8754, "Aurora", 12000, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/1f59f5593ce135325c1a034825cec18c.png~tplv-obj.png"),
CAMPING_NIGHT(6520, "Camping Night", 13999, "https://storage.streamdps.com/iblock/be3/be3c39c622d80d029c5e752134ac6978/c95701f2e894403ca47de971f2ced0d8.png"),
MAKE_IT_RAIN(5336, "Make it rain", 500, "https://storage.streamdps.com/iblock/770/770e03c64144e6d7830e884cd7140a8a/47af803e978121e760d649d47e67de50.png"),
@@ -657,10 +741,14 @@ public enum Gift {
STORMS_AT_SEA(9514, "Storms at sea", 2200, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/4918fbbdf220873dd8cae4c94d1ae037.png~tplv-obj.png"),
CHRISTMAS_WREATH_G(7527, "Christmas Wreath G", 10, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/7842b50135e089334fc40d9705bb53c7~tplv-obj.png"),
SUPERHERO_FIGHT(8814, "Superhero fight", 30000, "https://storage.streamdps.com/iblock/d6b/d6b1c955153c8f8c5048d6c8f0d1b418/97d04b889e64328e9ab07224f6072b5f.webp"),
COOKIE(6883, "Cookie", 5, "https://storage.streamdps.com/iblock/fd2/fd20c8c619b1d43efb9f2fe1923c48a7/45c056f74c9f214dc55d464eab43b224.webp"),
ELF_GDM_23(9363, "Elf GDM 23", 1, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/60e5289b379660cc562742cf987a2d35.png~tplv-obj.png"),
DOG_BONE(8108, "Dog Bone", 10, "https://storage.streamdps.com/iblock/8ba/8badf8e0a5bcbf8d98ed6c4fc0e16c69/b0a8a8020986eb564713c042d23f83b2.webp"),
RUSSIAN_CREPES(5547, "Russian Crepes", 5, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/8525a07c6bf16a74eee66e9ad119b3b8.png~tplv-obj.png"),
@@ -679,12 +767,16 @@ public enum Gift {
ICE_CREAM_CONE(5827, "Ice Cream Cone", 1, "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/968820bc85e274713c795a6aef3f7c67~tplv-obj.jpg"),
GOOD_MORNING(8269, "Good Morning", 399, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/5c1a28f3aa7eefc27491f3020748ce54~tplv-obj.png"),
FLY_LOVE(8248, "Fly Love", 19999, "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/a598ba4c7024f4d46c1268be4d82f901~tplv-obj.jpg"),
BEACH_DATE(6132, "Beach Date", 899, "https://storage.streamdps.com/iblock/504/504a5dfef033a7e90e4f07987b0c0f28/70ec484fc4c798d3e09a7fbcae83ee95.png"),
TRAVEL_WITH_YOU(6233, "Travel with You", 999, "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/753098e5a8f45afa965b73616c04cf89~tplv-obj.jpg"),
GINGEBREAD_MAN(9671, "Gingebread Man", 5, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/2399f65414f77419ec7d5e9274dc8e0e.png~tplv-obj.png"),
CHOCOLATE_STICK(6002, "Chocolate Stick", 1, "https://storage.streamdps.com/iblock/5c9/5c9487af2038c340fdbeb0b9ea4ff83e/b377ae8024881b93822f7b0a6bfe04e8.png"),
DJ_ALIEN(8988, "DJ Alien", 5000, "https://storage.streamdps.com/iblock/67c/67cd7b9372f25b4f3558eacdfb83dc8b/059b6bf7b8c268d525fd9295fac0eb61.webp"),
@@ -703,12 +795,16 @@ public enum Gift {
TULIP_BOX(5325, "Tulip Box", 200, "https://storage.streamdps.com/iblock/d44/d4471e5deb9cb5831f846ca4c9df9c5d/7d1236ecd67b3e655c3dfd72673a423d.png"),
COOL_(9583, "Cool!", 1, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/424c61f16c16919f169fd0352bd24661.png~tplv-obj.png"),
SQUIRREL(7213, "Squirrel", 1, "https://storage.streamdps.com/iblock/5c3/5c37dce1eab0d67386329f3a2920a874/38104bd52d316ea76464433b3b07dea7.webp"),
CHOC_CHIP_COOKIE(6416, "Choc Chip Cookie", 5, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/7dd2731de2e644301a329d3eb437b427~tplv-obj.png"),
METEOR_SHOWER(6563, "Meteor Shower", 3000, "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/71883933511237f7eaa1bf8cd12ed575~tplv-obj.jpg"),
GINGERMAN_PARTY(9668, "Gingerman Party", 1200, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/008a9554e736642f1b2dca9f198bb710.png~tplv-obj.png"),
CAKE(5720, "Cake", 20, "https://storage.streamdps.com/iblock/edb/edbe349c5a4be01ec1fbf2225d0f48dc/4169ef7f0263177384205df6663451c8.png"),
MARCH(7976, "March", 1, "https://storage.streamdps.com/iblock/ba4/ba44cb084cab8c9c63b4513a145813f4/56531d239586a3d4552859cb2b23314d.webp"),
@@ -721,12 +817,16 @@ public enum Gift {
STAR(6432, "Star", 99, "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/485175fda92f4d2f862e915cbcf8f5c4~tplv-obj.jpg"),
SPRING_TRAIN(8152, "Spring train", 3999, "https://storage.streamdps.com/iblock/035/035862dc0952468fc95f02995cec0f22/eeb69650806ea4c2e22558ef4b5e2b47.webp"),
DALLAH(8097, "Dallah", 10, "https://storage.streamdps.com/iblock/402/402ec89b471788374f63bd0d906e49c2/bbb7055a407d84bd3be843f5ca9fdc4b.webp"),
GB_NORTH_POLE(9657, "GB North Pole", 199, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/79715a53c41619e7b205eb26e57926d4.png~tplv-obj.png"),
CHRISTMAS_CAROUSEG(7525, "Christmas CarouseG", 2000, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/b5ba3941f7389da7495b659e888ea61a~tplv-obj.png"),
THUNDER_HAMMER(6635, "Thunder Hammer", 1, "https://storage.streamdps.com/iblock/401/401ff4c96ee1f2301db5a6fed5d53103/830012ba80bac708f9281417ede8696c.png"),
FESTIVE_TINY_DINY(9615, "Festive Tiny Diny", 15, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/f2a8c2967c7153e9077bb469f2e42317.png~tplv-obj.png"),
WOLF(8778, "Wolf", 5000, "https://storage.streamdps.com/iblock/70f/70fa80dd2d07f44f28db148328735a6b/68c7215817c6143ac33036933fcf777d.webp"),
DAISIES(6447, "Daisies", 1, "https://storage.streamdps.com/iblock/e11/e110e47562d77ab5fa26cc31e840f801/a4a1823ef2c1bc65c4dc2a4e82ec446b.png"),
@@ -735,6 +835,12 @@ public enum Gift {
CAMPING(6322, "Camping", 250, "https://storage.streamdps.com/iblock/9a9/9a9370a392311149be37e7c40c3e960d/ecb9dcdacf3a2ae0abef79baf0c4f41c.webp"),
LUCKY_AIRDROP_BOX(9717, "Lucky Airdrop Box", 999, "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/6ae56f08ae3ee57ea2dda0025bfd39d3.png~tplv-obj.jpg"),
GOLDEN(7921, "Golden", 299, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/b97f58dcb0250489ae98529bcb0542ca~tplv-obj.png"),
BALLET_DANCER(5549, "Ballet Dancer", 500, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/c09cc8ce49476d2c46e9c8af6189d5f4.png~tplv-obj.png"),
FAIRY_WINGS(9463, "Fairy Wings", 1, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/e504dc2f313b8c6df9e99a848e1b3a99.png~tplv-obj.png"),
TICKET(6856, "Ticket", 10, "https://storage.streamdps.com/iblock/434/434746bffe494ac6ad2eb5e7e4384955/92e426ea0b4d4a9f89d7e2786115cd20.webp"),
@@ -751,10 +857,14 @@ public enum Gift {
RIO_DE_JANEIRO(7218, "Rio de Janeiro", 9999, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/34c0eb43c3d50e8ab64408171ebbe733~tplv-obj.png"),
REALLY_CURIOUS(9703, "Really Curious", 1, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/793ba68723567b695b12f2ef08dc1484.png~tplv-obj.png"),
BLOOMING_RIBBONS(9498, "Blooming Ribbons", 1000, "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/f76750ab58ee30fc022c9e4e11d25c9d.png~tplv-obj.jpg"),
CORNFLOWER(8186, "Cornflower", 5, "https://storage.streamdps.com/iblock/025/025c50c390f6a12148a69728284c7298/36b50fe529db9d7db028b0774842e103.webp"),
SPIN_WITH_ME_GDM(9152, "Spin with me GDM", 199, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/149ac2e87d05490d7d251149cefe27a2.png~tplv-obj.png"),
ASMR_TIME_(6990, "ASMR Time ", 10, "https://storage.streamdps.com/iblock/49d/49dccba4525df92ed17678cc6ea47e95/b2c8c52d5294bb531d7d87a4c3ff97fe.webp"),
POOL_PARTY(5938, "Pool Party", 4999, "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/4147c5bcfad9623c693f83d5d6cba1f7~tplv-obj.jpg"),
@@ -793,6 +903,8 @@ public enum Gift {
SPACESHIP(6588, "Spaceship", 13999, "https://storage.streamdps.com/iblock/fb1/fb1096568dcc97c2575dec7441d0d651/245c4c7ce9bf5d5378586eb3a2478b42.webp"),
PINATA(6800, "Pinata", 699, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/c8a18d43dc9fb4598d7e991ebeb958ae~tplv-obj.png"),
BIRTHDAY_CROWN(9096, "Birthday Crown", 99, "https://storage.streamdps.com/iblock/c07/c073f2d950a252aa24b7343655208c8a/68f6af6dc16ab51396cef18f50a43792.webp"),
LION_S_MANE(7985, "Lion's Mane", 500, "https://storage.streamdps.com/iblock/267/2670a5a8c9666b7afffb3255c2c104ee/abe9a0e7a6ef8b83d94df90f3a356748.webp"),
@@ -819,8 +931,12 @@ public enum Gift {
NEW_UNIVERSE(9081, "New Universe", 1, "https://storage.streamdps.com/iblock/ff9/ff906a964a6ad9c4504438302d9354b8/3ee4796c239930c395afb3d7ef10295a.webp"),
SPARKLING_COUNTDOWN(9644, "Sparkling Countdown", 2024, "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/d6b4539ed9683707bdefe268f6575e74.png~tplv-obj.jpg"),
DRUMS(7882, "Drums", 1000, "https://storage.streamdps.com/iblock/449/449c40e5064f776737e24fd6460195a1/477a014b033108643c2d674b2cce2d0a.webp"),
ELFS_HAT_(9706, "Elfs Hat ", 299, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/f9857a040c92b34d6a261201a93c185f.png~tplv-obj.png"),
PUMPKIN_PIE(7396, "Pumpkin Pie", 5, "https://storage.streamdps.com/iblock/abf/abf5efb8fac6f64568b472c3afdb3e25/f85e4ef55b8c7d03f81351babd833c69.webp"),
EMAIL_MESSAGE(6199, "Email Message", 1000, "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/c959df6dbffd6f07849d22d2c3c07861~tplv-obj.jpg"),
@@ -831,10 +947,14 @@ public enum Gift {
HI_MARCH(7977, "Hi March", 88, "https://storage.streamdps.com/iblock/e22/e2266686271c7a90ff04517f248c6f73/0459d679c01a5bfa5a4be1d61ec81ec8.webp"),
CANDY_CANE_GUN(7498, "Candy Cane Gun", 799, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/58ef7964e32adc5fc47c5706a02e4ff0~tplv-obj.png"),
FANTASTIC(6813, "Fantastic", 5, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/a1b2204b06aa19d45a0338e9f0099ea7~tplv-obj.png"),
HEDGEHOG(6868, "Hedgehog", 299, "https://storage.streamdps.com/iblock/841/841e924150793d6961df0a1c89cc67ca/5886839b7de0b1289303081f9af380f8.webp"),
JAKARTA_ROUNDABOUT(6452, "Jakarta Roundabout", 16999, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/31f67910fc5858cf087da65746f1f9f3~tplv-obj.png"),
LIVE_FEST(9334, "LIVE Fest", 1, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/1e98afffef90ed4b2cc9c9ebb88e3608.png~tplv-obj.png"),
I_LOVE_TR(7139, "I LOVE TR", 1, "https://storage.streamdps.com/iblock/84d/84d68e92c471e7da792aa98d856c824c/7728ac60043efb9c96e2ce0f77dbef31.webp"),
@@ -869,10 +989,16 @@ public enum Gift {
CRYSTAL_BALL(6428, "Crystal Ball", 1700, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/7e4f9a99b7003ae05186f5324aae9fbf~tplv-obj.png"),
HAPPY_WEEKEND(8264, "Happy Weekend", 599, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/b04f104e717798235cd3edaa6703e6a3~tplv-obj.png"),
PHOENIX(7319, "Phoenix", 25999, "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/ef248375c4167d70c1642731c732c982~tplv-obj.jpg"),
BOUQUET_FLOWER(5780, "Bouquet Flower", 30, "https://storage.streamdps.com/iblock/ceb/cebb5d5f7004d6ccf9336ae20281be88/5061b1767c2325fe6704eb08d97c5cb8.png"),
PUMPKIN_SPICE_LATTE(9242, "Pumpkin Spice Latte", 10, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/0636d91615f7417ddd5f29438bf5debe~tplv-obj.png"),
SHIBA_COOKIE(9770, "Shiba Cookie", 10, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/4ea5282e7f61cbeee1214422d40ad407.png~tplv-obj.png"),
CHILI(7086, "Chili", 1, "https://storage.streamdps.com/iblock/4e4/4e476335c1d0a47efc33a40688d0fc75/ab85d5eccda4bf6509874c9533739b62.webp"),
GO_SNACKING(7021, "Go Snacking", 1, "https://storage.streamdps.com/iblock/666/6661d244aca6ec5f3de19372316e871e/f967ba18a333cd1489396cb608371824.webp"),
@@ -883,6 +1009,8 @@ public enum Gift {
CHILL(6704, "Chill", 5, "https://storage.streamdps.com/iblock/7df/7dfcee6b2702691bf9c8ca0966b3c4b1/144aa8fff9ce8c64aa7fcb507bf6c1cd.webp"),
GINGERBREAD_MAN(9656, "Gingerbread man", 5, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/af01db3e3cb9f54ea2cb421fab6062bc.png~tplv-obj.png"),
TAKE_A_DRIVE(7624, "Take a Drive", 1200, "https://storage.streamdps.com/iblock/fb5/fb50bcd0bb83c3a338d7d5196a7e987c/aa0e7affca4b6d34877244af7f5b611c.webp"),
TAKE_A_DRIVE_7631(7631, "Take a Drive", 1200, "https://storage.streamdps.com/iblock/c5b/c5b1ae3782864918bcb70d9e92046b87/8f3b4f952004f1aaef4bccfd69b19568.webp"),
@@ -901,6 +1029,8 @@ public enum Gift {
BALALAIKA(5927, "Balalaika", 100, "https://storage.streamdps.com/iblock/d88/d88bc38371769262c006dccbaa43c9ff/1e0f5adda0546879e07126492ba6001c.webp"),
POPCORN(9111, "Popcorn", 5, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/676d2d4c31a8979f1fd06cdf5ecd922f~tplv-obj.png"),
DRACO(8613, "Draco", 5000, "https://storage.streamdps.com/iblock/48f/48f1a8d280e271929718525560ad42a8/3021d84608e0c5da388f1f6534011a6f.webp"),
SPEEDSTER(8418, "Speedster", 15000, "https://storage.streamdps.com/iblock/96a/96a5a249a1701c3c03e0b2427bad3b2f/63fb5582c89c17f275fc99505505b719.webp"),
@@ -941,8 +1071,6 @@ public enum Gift {
COOPER_SKATES_HOME(6865, "Cooper Skates Home", 599, "https://storage.streamdps.com/iblock/041/04184b09ec8e7bf137d33cf57ce4eec9/3c2e360b023b9980e54e9d9a394883b9.webp"),
EXCLUSIVE_JET(9523, "Exclusive Jet", 5000, "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/1cc167a00aa4d5dfe48018afb38c3daa.png~tplv-obj.jpg"),
BIRTHDAY_PARTY(6787, "Birthday Party", 6999, "https://storage.streamdps.com/iblock/135/135b2ac0877de059f56e510b0ac70d08/07285ba7471fb98743bfe308d0b58ce2.webp"),
BIRTHDAY_PARTY_9095(9095, "Birthday Party", 6999, "https://storage.streamdps.com/iblock/d0d/d0d1164a9ed81239b70cb25b93927023/d0dba293643c67dc33c1f4dda04e5b50.webp"),
@@ -963,10 +1091,14 @@ public enum Gift {
STAR_THRONE_8420(8420, "Star Throne", 7999, "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/30063f6bc45aecc575c49ff3dbc33831~tplv-obj.jpg"),
MISTLETOE_GDM_23(7475, "Mistletoe GDM 23", 199, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/3527969b8c27e3194e61ff0787a9c3c2~tplv-obj.png"),
PLANET(5954, "Planet", 15000, "https://storage.streamdps.com/iblock/dc5/dc50bbe9b153d9f714919d386325a223/b296c2101cb24bc65e8abd2977d6c123.png"),
DISCO_BALL(5540, "Disco Ball", 1000, "https://storage.streamdps.com/iblock/3e5/3e5e6d701c936bef5b85a0315b841184/e46e6c47d88c9bb81d27eb700456137a.webp"),
DISCO_BALL_8250(8250, "Disco ball", 1000, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/a53d3ef956eb2f1aa7a7db46024c70bb~tplv-obj.png"),
OUD(5301, "Oud", 300, "https://storage.streamdps.com/iblock/6ba/6ba340c152f9154c8d7c45d18bcb5914/6be86ee5d8a8ebaa17d93b766589b151.png"),
SUITCASE(8597, "Suitcase", 199, "https://storage.streamdps.com/iblock/50f/50f04937063753d6de255d2b5a080c1c/4f101c7c50ddbe8bd26a2ce5f8c16896.webp"),
@@ -981,6 +1113,8 @@ public enum Gift {
DAFFODILS(6435, "Daffodils", 99, "https://storage.streamdps.com/iblock/2ed/2edf26fa90a7b3ca44c0d7c77a765c77/c333c68579488e9a36f4130481932b7c.png"),
MARACAS(7032, "Maracas", 1, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/00204efcf0573192ad5d872c7beeaf5b~tplv-obj.png"),
TINY_DINY(6560, "Tiny Diny", 10, "https://storage.streamdps.com/iblock/504/50412c09b6d36020e28ee09ceb45f22b/aa96e43206d46ed5f25e8f476f67da45.png"),
TINY_DINY_7591(7591, "Tiny Diny", 10, "https://storage.streamdps.com/iblock/b24/b24309d4ea6722875678e492ae12fb3f/864ac7928a78b43be2d1ee93915a53f5.webp"),
@@ -993,10 +1127,16 @@ public enum Gift {
TTEOKBOKKI(5645, "Tteokbokki", 5, "https://storage.streamdps.com/iblock/81c/81ca5954462f21f506095fe410dd2aaf/c07e9b0bb8d0559874b780495cc0e451.png"),
CRYSTAL_HEART(5559, "Crystal Heart", 499, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/ae46ac6582a606009643440fe4138eb4.png~tplv-obj.png"),
LOLLIPOP(5657, "Lollipop", 10, "https://storage.streamdps.com/iblock/857/85755cf3d5e2e5349efff7eeedbfff46/b5e02fcff0a73b906d530028d460e59d.png"),
FLOWER_FESTIVAL(8442, "Flower Festival", 199, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/9bfe63e39b581a69ff944758c3eae5a0~tplv-obj.png"),
GRAPES(7234, "Grapes", 1, "https://storage.streamdps.com/iblock/442/442580106ac8748b79ef450eb25b5981/df624c619c48b583adee184bca134c80.webp"),
TSAR(5524, "Tsar", 100, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/cb1c3e6263d4b6c08301f8798dcb5a9b.png~tplv-obj.png"),
LLAMA_GREETINGS(6531, "Llama Greetings", 299, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/a6b95ce6350f5f4bdff6880ac6993789~tplv-obj.png"),
SAM_THE_WHALE(8391, "Sam the Whale", 30000, "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/f48a1887eb88238738996bb997b31c0f.png~tplv-obj.jpg"),
@@ -1017,6 +1157,8 @@ public enum Gift {
GOOD_NIGHT(8268, "Good Night", 399, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/b7b55087141bd5f965eb31a99a5f157b~tplv-obj.png"),
TRAIN_TO_2024(9642, "Train to 2024", 12024, "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/0cb12913e0a96bbcc45f97b450e74cd3.png~tplv-obj.jpg"),
ALIEN_PEACE_SIGN(7831, "Alien Peace Sign", 1, "https://storage.streamdps.com/iblock/5f7/5f7b29f5c7a4ca3a4dbbe8dc0e195459/cd83433a0f1697a0b66a891cbd7cf1af.webp"),
THE_MAGIC_LAMP(7161, "The Magic Lamp", 1000, "https://storage.streamdps.com/iblock/e0d/e0d45fccd69220f321531383d97f51fc/4296cc4b886f31bb5b2cf106ebf640ab.webp"),
@@ -1025,6 +1167,8 @@ public enum Gift {
TIARA(8496, "Tiara", 299, "https://storage.streamdps.com/iblock/1b1/1b1ee7b697bae41ee2cbf834d1f1099e/303eec791a710c2417bb5075529681d9.webp"),
THE_CROWN(8207, "The Crown", 199, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/5bf798f92fe96ba53c0f4d28f052f9bb~tplv-obj.png"),
COFFEE(5333, "Coffee", 1, "https://storage.streamdps.com/iblock/920/920b64634d946a2238950c353c16df81/0fe22d9bdee1bd6d9d77f66bcd8cf45a.png"),
COFFEE_5479(5479, "Coffee", 1, "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/02492214b9bd50fee2d69fd0d089c025.png~tplv-obj.jpg"),
@@ -1033,6 +1177,8 @@ public enum Gift {
COFFEE_5961(5961, "Coffee", 30, "https://storage.streamdps.com/iblock/87b/87b2a811f1ca8c09060e2bd63a3b3be7/ae6288eb1eab67474807c64b9d69b5e6.webp"),
MAGIC_HAT(6393, "Magic Hat", 299, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/b156ffd21bb3849a52144ab1688bbc43~tplv-obj.png"),
HAND_HEART(5924, "Hand Heart", 100, "https://storage.streamdps.com/iblock/5ce/5cebff2a4b737063778ac5374e9e4792/f9bf5c945eb61002916feff420a1cc3a.png"),
HAND_HEART_6968(6968, "Hand Heart", 100, "https://storage.streamdps.com/iblock/9f0/9f0bfed08f1d3b9e852469d6a4debeda/519497b062ded1019c958d5d0b352a7e.webp"),
@@ -1063,6 +1209,8 @@ public enum Gift {
TIKTOK_CROWN(8873, "TikTok Crown", 299, "https://storage.streamdps.com/iblock/a79/a790613bdf2e83725d0519bbf289529d/83bb670c15ab91b9192c50300f4c8054.webp"),
KFC_CHICKEN(9771, "KFC Chicken", 5, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/f9d59ccd2328b8a46841b3b1c87d9e55.png~tplv-obj.png"),
LOVED(6705, "Loved", 5, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/2a41781b0a29ba3c409c5dd83eed07f8~tplv-obj.png"),
ON_FIRE(6840, "On Fire", 200, "https://storage.streamdps.com/iblock/cba/cba95075d6b63b84fbc52abb9d1d8208/d93ecc0b966bf972f01e77339a68e124.webp"),
@@ -1081,22 +1229,38 @@ public enum Gift {
FERRIS_WHEEL(5652, "Ferris Wheel", 3000, "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/3c7291ad4c2a6d4f70505c3e296ecebe~tplv-obj.jpg"),
DE_NORTH_POLE(9658, "DE North Pole", 199, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/130e17b5b561a93cefbd236586881477.png~tplv-obj.png"),
RUGBY_BALL(6249, "Rugby Ball", 10, "https://storage.streamdps.com/iblock/d53/d53d3efae1c69b949373db455da077cf/fa22f0507a304597b013268524a8573f.png"),
TROPHY(5712, "Trophy", 500, "https://storage.streamdps.com/iblock/26f/26f17d2bc63c5e3f218ea2f25b245fa2/95d88e55486d8188f4b73c75def4354c.png"),
ACROSS_THE_BOARD(8793, "Across the board", 450, "https://storage.streamdps.com/iblock/285/285070af9d4f72b74e7d74c22157f2d9/67d9fa3239a7f9a09ef78c832a66e624.webp"),
GOOD_AFTERNOON(8266, "Good Afternoon", 399, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/bff3b908c4dd9cf19ab431cc99dc7940~tplv-obj.png"),
GARDEN_GNOME(7002, "Garden Gnome", 1, "https://storage.streamdps.com/iblock/c1e/c1efcf386f4ffc5626e0be1ef1ecd93a/210fa9d66c1f0c1968608b40c4e698ea.webp"),
MIMI___FIFI(9672, "Mimi & Fifi", 5000, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/0a72d0084695d03586fea7d854dc3a47.png~tplv-obj.png"),
CANDY_CANE(9698, "Candy Cane", 1, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/1fa0a4ed666304c78a46de200b85c84b.png~tplv-obj.png"),
FAKE_SMILE(9536, "Fake smile", 299, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/35ce62173962e33834703212d0b845a7.png~tplv-obj.png"),
SO_CUTE(9355, "So cute", 1, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/d40d31241efcf57c630e894bb3007b8a.png~tplv-obj.png"),
LILI_THE_LEOPARD(9467, "Lili the Leopard", 6599, "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/7be03e1af477d1dbc6eb742d0c969372.png~tplv-obj.jpg"),
MUSIC_BOX(5964, "Music Box", 2399, "https://storage.streamdps.com/iblock/f01/f01f2da5e18be863eb7e3a1375bb6206/499e06f8f76e5e90964184c25365cdec.png"),
ELF_S_HAT_(9625, "Elf's Hat ", 299, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/f9857a040c92b34d6a261201a93c185f.png~tplv-obj.png"),
PRINCE(7398, "Prince", 500, "https://storage.streamdps.com/iblock/38b/38b4963191222c66267858149e662b7d/d98b625b2a3a261d2c12caaae61b479f.webp"),
DASH(6757, "Dash", 299, "https://storage.streamdps.com/iblock/b35/b356ce71b1272dffc836a14df85700d2/16e177319d9f5cf312440139715612f5.webp"),
SNOWGLOBE(9688, "Snowglobe", 499, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/ea5ac5f8e186897456bed2e78fc78ca5.png~tplv-obj.png"),
BABY_FOX(8552, "Baby fox", 20, "https://storage.streamdps.com/iblock/b7d/b7df9be24aa630c9b04db3974f103a73/17a445fd5256e13281e64c718b5112fe.webp"),
RAYA_RICE(6383, "Raya Rice", 1, "https://storage.streamdps.com/iblock/e0c/e0c375df5bdce1c926f46244ced54ecc/1bd688843c1c24370b8c4a74686c2c0d.png"),
@@ -1119,12 +1283,16 @@ public enum Gift {
GOLD_NECKLACE(5599, "Gold necklace", 200, "https://storage.streamdps.com/iblock/aa2/aa26035cd47797211a9ce1b5e51fd7ac/85e66a118c564c318e369974510f371d.png"),
HOLIDAY_CAROUSEL(9678, "Holiday Carousel", 2000, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/b5ba3941f7389da7495b659e888ea61a.png~tplv-obj.png"),
MPL_TROPHY(7119, "MPL Trophy", 450, "https://storage.streamdps.com/iblock/981/9816c81ee3e2fdc62ac221051e9ec290/75707f488be3f80faf5affd1cef38deb.webp"),
OCTOPUS(8417, "Octopus", 10000, "https://storage.streamdps.com/iblock/419/4197c396a3fcdd28f0477d9af50cd964/1196ad0f243ca976832319a46c7935ed.webp"),
PIM_BEAR(9043, "Pim Bear", 1500, "https://storage.streamdps.com/iblock/204/2043f85b8f2e2ee638ff3a1799eda329/2319b052e0e64799842751d9fee4d438.webp"),
FALLING_FOR_YOU(8005, "Falling For You", 299, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/a198bd39d2511dbba6a68867740e3ff9~tplv-obj.png"),
SIGNATURE_JET(7124, "Signature Jet", 4888, "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/fe27eba54a50c0a687e3dc0f2c02067d~tplv-obj.jpg"),
CAP(6104, "Cap", 99, "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/6c2ab2da19249ea570a2ece5e3377f04~tplv-obj.jpg"),
@@ -1141,6 +1309,8 @@ public enum Gift {
SUPERB_TEAM(8202, "Superb Team", 1, "https://storage.streamdps.com/iblock/e6e/e6e8cdd5e84f701dc627b8cc3e280d4c/29ce845878feb46152b20a75a3259d56.webp"),
MAGIC_POTION(7105, "Magic Potion", 499, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/e055625e9239df7e833702c768e033d2~tplv-obj.png"),
GLOWING_JELLYFISH(8978, "Glowing Jellyfish", 1000, "https://storage.streamdps.com/iblock/e65/e65b1f71b4fe5709b454299439cb2674/36471857a2ba78694be934a54a0fa8d2.webp"),
RAINING_GIFTS(8769, "Raining gifts", 999, "https://storage.streamdps.com/iblock/916/91661303a8dc3660acaf2f4e47a94f75/221a1f185676496ebcdbaf55f90aeb70.webp"),
@@ -1163,8 +1333,6 @@ public enum Gift {
CAKE_SLICE(6784, "Cake Slice", 1, "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/f681afb4be36d8a321eac741d387f1e2~tplv-obj.jpg"),
EXCLUSIVE_SPARK(9522, "Exclusive Spark", 1000, "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/f0bda1eb6856e2feea9cfcb6c575c8a0.png~tplv-obj.jpg"),
CROCODILE(8740, "Crocodile", 10, "https://storage.streamdps.com/iblock/4e2/4e2d9df24c472158b8ed93546fc73b16/75722a173b75d601e0a80a679902529f.webp"),
LOVE_YOU_6671(6671, "Love You", 199, "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/134e51c00f46e01976399883ca4e4798~tplv-obj.jpg"),
@@ -1199,6 +1367,8 @@ public enum Gift {
HOT_AIR_BALLOON(5976, "Hot Air Balloon", 999, "https://storage.streamdps.com/iblock/33a/33a5eb58a8dd71677072c9482aad209a/61be5fe5d3d639e3729edbf003a536c7.png"),
RABBIT_AND_MOCHI(9303, "Rabbit and Mochi", 999, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/213ef2549fbb10ec783c95a41d28cf0a.png~tplv-obj.png"),
TEA(5303, "Tea", 50, "https://storage.streamdps.com/iblock/240/24051d7263606ed2b02f24f8455cb0a5/4f1cc7de604a1369c5770cc02cbee920.png"),
TEA_6726(6726, "Tea", 20, "https://storage.streamdps.com/iblock/b0b/b0ba111b6319a8c9e384d5ca7b814e4c/6cd6f620512cd42711bc1235124b3265.webp"),
@@ -1211,6 +1381,8 @@ public enum Gift {
PIRATE_S_SHIP(7598, "Pirates Ship", 15000, "https://storage.streamdps.com/iblock/475/4753e54cae562b34edbf1a157cd60b21/722409ec69cfaf707d611b0987799296.webp"),
DOMBRA(6426, "Dombra", 20, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/ccd9fea1988521d1e81051a916800d6c~tplv-obj.png"),
GRIFFIN(7987, "Griffin", 25999, "https://storage.streamdps.com/iblock/609/6092240118fdb3ad46036c0533dd23c8/0f700fd4e875174d01ad04a8db2ae94e.webp"),
BEACH_HUT(6719, "Beach Hut", 5000, "https://storage.streamdps.com/iblock/227/22716035cef6112f66035eca2b60fa31/ffce7cd46aaa6c2b27ccba610cf35a39.webp"),

View File

@@ -43,13 +43,12 @@ public class User {
private long followers;
private List<Badge> badges;
@Getter(AccessLevel.NONE)
private Set<UserAttribute> attributes;
private final Set<UserAttribute> attributes = new HashSet<>();
public List<UserAttribute> getAttributes() {
return attributes.stream().toList();
}
public boolean hasAttribute(UserAttribute userFlag) {
return attributes.contains(userFlag);
}
@@ -106,7 +105,6 @@ public class User {
this.following = following;
this.followers = followers;
this.badges = badges;
this.attributes = new HashSet<>();
}
public User(Long id,
@@ -123,14 +121,12 @@ public class User {
this.following = following;
this.followers = followers;
this.badges = badges;
this.attributes = new HashSet<>();
}
public User(Long userId,
String nickName) {
this.id = userId;
this.name = nickName;
this.attributes = new HashSet<>();
}
public User(Long userId,
@@ -213,4 +209,4 @@ public class User {
0,
List.of(Badge.empty()));
}
}
}

View File

@@ -0,0 +1,57 @@
/*
* 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.data.requests;
import io.github.jwdeveloper.tiktok.data.models.gifts.Gift;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Getter;
import java.util.List;
public class GiftsData
{
@Getter
public final class Request
{
}
@Getter
@AllArgsConstructor
public static final class Response
{
private String json;
private List<GiftModel> gifts;
}
@Data
public static class GiftModel
{
private int id;
private String name;
private int diamondCost;
private String image;
}
}

View File

@@ -0,0 +1,47 @@
/*
* 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.data.requests;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastResponse;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Getter;
import java.net.URI;
import java.time.Duration;
public class LiveConnectionData {
@Getter
@AllArgsConstructor
public static class Request {
private String roomId;
}
@Getter
@AllArgsConstructor
public static class Response {
private String websocketCookies;
private URI websocketUrl;
private WebcastResponse webcastResponse;
}
}

View File

@@ -20,26 +20,35 @@
* 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.live;
package io.github.jwdeveloper.tiktok.data.requests;
import io.github.jwdeveloper.tiktok.data.models.users.User;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Getter;
@Data
public class LiveRoomMeta {
public class LiveData {
@Getter
@AllArgsConstructor
public static class Request {
private String roomId;
}
private LiveRoomStatus status;
private boolean ageRestricted;
private String titie;
private int likeCount;
private int totalViewers;
private int viewers;
private User host;
@Data
public static class Response {
private String json;
private LiveStatus liveStatus;
private String title;
private int likes;
private int viewers;
private int totalViewers;
private boolean ageRestricted;
private User host;
}
public enum LiveRoomStatus
{
public enum LiveStatus {
HostNotFound,
HostOnline,
HostOffline
HostOffline,
}
}

View File

@@ -0,0 +1,58 @@
/*
* 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.data.requests;
import lombok.*;
public class LiveUserData {
@Getter
@AllArgsConstructor
public static class Request {
private String userName;
}
@Getter
@AllArgsConstructor
public static class Response {
private String json;
private UserStatus userStatus;
private String roomId;
private long startedAtTimeStamp;
public boolean isLiveOnline() {
return userStatus == LiveUserData.UserStatus.Live || userStatus == LiveUserData.UserStatus.LivePaused;
}
public boolean isHostNameValid() {
return userStatus != LiveUserData.UserStatus.NotFound;
}
}
public enum UserStatus {
NotFound,
Offline,
LivePaused,
Live,
}
}

View File

@@ -20,24 +20,16 @@
* 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.data.dto;
package io.github.jwdeveloper.tiktok.data.requests;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class TikTokUserInfo
public class SignServerResponse
{
UserStatus userStatus;
private String signedUrl;
String roomId;
public enum UserStatus
{
NotFound,
Offline,
LivePaused,
Live
}
private String userAgent;
}

View File

@@ -0,0 +1,112 @@
/*
* 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.data.settings;
import lombok.Getter;
import lombok.Setter;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
import java.util.function.Consumer;
public class HttpClientSettings {
@Getter
final Map<String, Object> params;
@Getter
final Map<String, String> headers;
@Getter
final Map<String, String> cookies;
@Getter
@Setter
ProxyClientSettings proxyClientSettings;
@Getter
Consumer<HttpClient.Builder> onClientCreating;
@Getter
Consumer<HttpRequest.Builder> onRequestCreating;
@Setter
@Getter
Duration timeout;
public HttpClientSettings() {
this.params = new TreeMap<>();
this.headers = new HashMap<>();
this.cookies = new HashMap<>();
this.timeout = Duration.ofSeconds(2);
this.proxyClientSettings = new ProxyClientSettings();
this.onClientCreating = (x) -> {
};
this.onRequestCreating = (x) -> {
};
}
/**
* @param consumer Use to configure proxy settings for http client
*/
public void configureProxy(Consumer<ProxyClientSettings> consumer) {
proxyClientSettings.setEnabled(true);
consumer.accept(proxyClientSettings);
}
/**
* @param onRequestCreating Every time new Http request in created this method will be triggered
* use to modify request
*/
public void onRequestCreating(Consumer<HttpRequest.Builder> onRequestCreating) {
this.onRequestCreating = onRequestCreating;
}
/**
* @param onClientCreating Every time new instance of Http client request in created this method will be triggered
* use to modify http client
*/
public void onClientCreating(Consumer<HttpClient.Builder> onClientCreating) {
this.onClientCreating = onClientCreating;
}
@Override
public HttpClientSettings clone() {
var newSettings = new HttpClientSettings();
newSettings.setTimeout(this.getTimeout());
newSettings.onRequestCreating(this.onRequestCreating);
newSettings.onClientCreating(this.onClientCreating);
newSettings.getHeaders().putAll(new TreeMap<>(this.headers));
newSettings.getCookies().putAll(new TreeMap<>(this.cookies));
newSettings.getParams().putAll(new TreeMap<>(this.params));
newSettings.proxyClientSettings = this.proxyClientSettings;
return newSettings;
}
}

View File

@@ -20,7 +20,9 @@
* 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;
package io.github.jwdeveloper.tiktok.data.settings;
import lombok.Data;
import java.time.Duration;
import java.util.HashMap;
@@ -28,44 +30,74 @@ import java.util.Map;
import java.util.TreeMap;
import java.util.logging.Level;
public class Constants {
@Data
public class LiveClientSettings {
/**
* Web-URL for TikTok
* ISO-Language for Client
*/
public static final String TIKTOK_URL_WEB = "https://www.tiktok.com/";
/**
* WebCast-BaseURL for TikTok
*/
public static final String TIKTOK_URL_WEBCAST = "https://webcast.tiktok.com/webcast/";
/**
* Signing API by Isaac Kogan
* https://github-wiki-see.page/m/isaackogan/TikTokLive/wiki/All-About-Signatures
*/
public static final String TIKTOK_SIGN_API = "https://tiktok.eulerstream.com/webcast/sign_url";
private String clientLanguage;
/**
* Default TimeOut for Connections
* Whether to Retry if Connection Fails
*/
public static final int DEFAULT_TIMEOUT = 20;
private boolean retryOnConnectionFailure;
/**
* Default Settings for Client
* Before retrying connect, wait for select amount of time
*/
public static ClientSettings DefaultClientSettings() {
var clientSettings = new ClientSettings();
clientSettings.setTimeout(Duration.ofSeconds(DEFAULT_TIMEOUT));
private Duration retryConnectionTimeout;
/**
* Whether to print Logs to Console
*/
private boolean printToConsole = true;
/**
* LoggingLevel for Logs
*/
private Level logLevel;
/**
* Optional: Use it if you need to change TikTok live hostname in builder
*/
private String hostName;
/**
* Parameters used in requests to TikTok api
*/
private HttpClientSettings httpSettings;
/**
* Optional: Sometimes not every messages from chat are send to TikTokLiveJava to fix this issue you can set sessionId
* documentation how to obtain sessionId https://github.com/isaackogan/TikTok-Live-Connector#send-chat-messages
*/
private String sessionId;
/**
* Optional: By default roomID is fetched before connect to live, but you can set it manually
*
*/
private String roomId;
public static LiveClientSettings createDefault()
{
var httpSettings = new HttpClientSettings();
httpSettings.getParams().putAll(DefaultClientParams());
httpSettings.getHeaders().putAll(DefaultRequestHeaders());
httpSettings.setTimeout(Duration.ofSeconds(3));
var clientSettings = new LiveClientSettings();
clientSettings.setClientLanguage("en-US");
clientSettings.setRetryOnConnectionFailure(false);
clientSettings.setRetryConnectionTimeout(Duration.ofSeconds(1));
clientSettings.setPrintToConsole(false);
clientSettings.setLogLevel(Level.ALL);
clientSettings.setClientParameters(Constants.DefaultClientParams());
clientSettings.setHttpSettings(httpSettings);
return clientSettings;
}
/**
* Default Parameters for HTTP-Request
*/
@@ -104,26 +136,22 @@ public class Constants {
clientParams.put("webcast_sdk_version", "1.3.0");
clientParams.put("update_version_code", "1.3.0");
return clientParams;
}
/**
* Default Headers for HTTP-Request
*/
public static Map<String, String> DefaultRequestHeaders() {
var headers = new HashMap<String, String>();
headers.put("authority","www.tiktok.com");
headers.put("Connection", "keep-alive");
headers.put("authority", "www.tiktok.com");
headers.put("Cache-Control", "max-age=0");
headers.put("Accept", "text/html,application/json,application/protobuf");
headers.put("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.63 Safari/537.36");
headers.put("Referer", "https://www.tiktok.com/");
headers.put("Origin", "https://www.tiktok.com");
headers.put("Accept-Language", "en-US,en; q=0.9");
headers.put("Accept-Encoding", "gzip, deflate");
return headers;
}
}
}

View File

@@ -0,0 +1,123 @@
/*
* 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.data.settings;
import io.github.jwdeveloper.tiktok.data.dto.ProxyData;
import lombok.*;
import java.net.*;
import java.util.*;
import java.util.function.Consumer;
@Getter
@Setter
public class ProxyClientSettings implements Iterator<ProxyData>
{
private boolean enabled, lastSuccess;
private Rotation rotation = Rotation.CONSECUTIVE;
private final List<ProxyData> proxyList = new ArrayList<>();
private int index = 0;
private boolean autoDiscard = true;
private Proxy.Type type = Proxy.Type.DIRECT;
private Consumer<ProxyData> onProxyUpdated = x -> {};
public boolean addProxy(String addressPort) {
return proxyList.add(ProxyData.map(addressPort));
}
public boolean addProxy(String address, int port) {
return addProxy(new InetSocketAddress(address, port));
}
public boolean addProxy(InetSocketAddress inetAddress) {
return proxyList.add(new ProxyData(inetAddress.getHostString(), inetAddress.getPort()));
}
public void addProxies(List<String> list) {
list.forEach(this::addProxy);
}
@Override
public boolean hasNext() {
return !proxyList.isEmpty();
}
@Override
public ProxyData next() {
return lastSuccess ? proxyList.get(index) : rotate();
}
public ProxyData rotate() {
var nextProxy = switch (rotation)
{
case CONSECUTIVE -> {
index = (index+1) % proxyList.size();
yield proxyList.get(index).clone();
}
case RANDOM -> {
index = new Random().nextInt(proxyList.size());
yield proxyList.get(index).clone();
}
case NONE -> proxyList.get(index).clone();
};
onProxyUpdated.accept(nextProxy);
return nextProxy;
}
@Override
public void remove() {
proxyList.remove(index);
lastSuccess = false; // index is no longer valid and lastSuccess needs falsified
}
public void setIndex(int index) {
if (index == 0 && proxyList.isEmpty())
this.index = 0;
else {
if (index < 0 || index >= proxyList.size())
throw new IndexOutOfBoundsException("Index " + index + " exceeds list of size: " + proxyList.size());
this.index = index;
}
}
public ProxyClientSettings clone()
{
ProxyClientSettings settings = new ProxyClientSettings();
settings.setEnabled(enabled);
settings.setRotation(rotation);
settings.setIndex(index);
settings.setType(type);
settings.setOnProxyUpdated(onProxyUpdated);
proxyList.forEach(proxyData -> settings.addProxy(proxyData.getAddress(), proxyData.getPort()));
return settings;
}
public enum Rotation
{
/** Rotate addresses consecutively, from proxy 0 -> 1 -> 2 -> ...etc. */
CONSECUTIVE,
/** Rotate addresses randomly, from proxy 0 -> 69 -> 420 -> 1 -> ...etc. */
RANDOM,
/** Don't rotate addresses at all, pin to the indexed address. */
NONE
}
}

View File

@@ -0,0 +1,49 @@
/*
* 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.exceptions;
/*
* Happens while bad response from http proxy request to TikTok
*/
public class TikTokProxyRequestException extends TikTokLiveException
{
public TikTokProxyRequestException() {
}
public TikTokProxyRequestException(String message) {
super(message);
}
public TikTokProxyRequestException(String message, Throwable cause) {
super(message, cause);
}
public TikTokProxyRequestException(Throwable cause) {
super(cause);
}
public TikTokProxyRequestException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}

View File

@@ -20,33 +20,26 @@
* 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;
package io.github.jwdeveloper.tiktok.exceptions;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class TikTokCookieJar {
private final Map<String, String> cookies;
public TikTokCookieJar() {
cookies = new HashMap<>();
public class TikTokSignServerException extends TikTokLiveRequestException
{
public TikTokSignServerException() {
}
public String get(String key) {
return cookies.get(key);
public TikTokSignServerException(String message) {
super(message);
}
public void set(String key, String value) {
cookies.put(key, value);
public TikTokSignServerException(String message, Throwable cause) {
super(message, cause);
}
public String parseCookies()
{
var sb = new StringBuilder();
for(var entry : cookies.entrySet())
{
sb.append(entry.getKey()).append("=").append(entry.getValue()).append(";");
}
return sb.toString();
public TikTokSignServerException(Throwable cause) {
super(cause);
}
public TikTokSignServerException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}

View File

@@ -0,0 +1,64 @@
/*
* 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;
import io.github.jwdeveloper.tiktok.data.requests.GiftsData;
import io.github.jwdeveloper.tiktok.data.requests.LiveConnectionData;
import io.github.jwdeveloper.tiktok.data.requests.LiveData;
import io.github.jwdeveloper.tiktok.data.requests.LiveUserData;
public interface LiveHttpClient {
/**
* @return list of gifts that are available in your country
*/
GiftsData.Response fetchGiftsData();
/**
* Returns information about user that is having a livestream
*
* @param userName
* @return
*/
LiveUserData.Response fetchLiveUserData(String userName);
LiveUserData.Response fetchLiveUserData(LiveUserData.Request request);
/**
* @param roomId can be obtained from browsers cookies or by invoked fetchLiveUserData
* @return
*/
LiveData.Response fetchLiveData(String roomId);
LiveData.Response fetchLiveData(LiveData.Request request);
/**
* @param roomId can be obtained from browsers cookies or by invoked fetchLiveUserData
* @return
*/
LiveConnectionData.Response fetchLiveConnectionData(String roomId);
LiveConnectionData.Response fetchLiveConnectionData(LiveConnectionData.Request request);
}

View File

@@ -42,6 +42,7 @@ public interface LiveRoomInfo
*/
int getTotalViewersCount();
int getLikesCount();
long getStartTime();
boolean isAgeRestricted();
String getRoomId();
String getHostName();
@@ -49,4 +50,4 @@ public interface LiveRoomInfo
User getHostUser();
List<RankingUser> getUsersRanking();
ConnectionState getConnectionState();
}
}

View File

@@ -22,15 +22,13 @@
*/
package io.github.jwdeveloper.tiktok.live.builder;
import io.github.jwdeveloper.tiktok.ClientSettings;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent;
import io.github.jwdeveloper.tiktok.data.settings.LiveClientSettings;
import io.github.jwdeveloper.tiktok.listener.TikTokEventListener;
import io.github.jwdeveloper.tiktok.live.LiveClient;
import io.github.jwdeveloper.tiktok.mappers.TikTokMapper;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
import java.util.function.Function;
public interface LiveClientBuilder extends EventsBuilder<LiveClientBuilder> {
@@ -48,11 +46,11 @@ public interface LiveClientBuilder extends EventsBuilder<LiveClientBuilder> {
/**
* Configuration of client settings
* @see ClientSettings
* @see LiveClientSettings
* @param onConfigure
* @return
*/
LiveClientBuilder configure(Consumer<ClientSettings> onConfigure);
LiveClientBuilder configure(Consumer<LiveClientSettings> onConfigure);
/**
* @see TikTokEventListener

View File

@@ -36,6 +36,11 @@ public class ProtoBufferObject {
this.fields = new TreeMap<>();
}
public Object getField(int index)
{
return fields.get(index);
}
public void addField(int index, String type, Object value) {
fields.put(index, new ProtoBufferField(type, value));
}

View File

@@ -22,11 +22,11 @@
*/
package io.github.jwdeveloper.tiktok.websocket;
import io.github.jwdeveloper.tiktok.data.requests.LiveConnectionData;
import io.github.jwdeveloper.tiktok.live.LiveClient;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastResponse;
public interface SocketClient {
void start(WebcastResponse webcastResponse, LiveClient tikTokLiveClient);
void start(LiveConnectionData.Response webcastResponse, LiveClient tikTokLiveClient);
void stop();
}

View File

@@ -976,131 +976,146 @@ message FanTicketRoomNoticeContent {
}
message LinkerAcceptNoticeContent {
int64 FromUserId = 1;
int64 FromRoomId = 2;
int64 ToUserId = 3;
int64 fromUserId = 1;
int64 fromRoomId = 2;
int64 toUserId = 3;
}
message LinkerCancelContent {
int64 FromUserId = 1;
int64 ToUserId = 2;
int64 CancelType = 3;
int64 ActionId = 4;
int64 fromUserId = 1;
int64 toUserId = 2;
int64 cancelType = 3;
int64 actionId = 4;
}
message ListUser {
User user = 1;
int64 linkmicId = 2;
string linkmicIdStr = 3;
int64 linkStatus = 4; // Enum
LinkType linkType = 5; // Enum
int32 userPosition = 6;
int32 silenceStatus = 7; // Enum
int64 modifyTime = 8;
int64 linkerId = 9;
int32 roleType = 10; // Enum
enum LinkType {
LINK_UNKNOWN = 0;
AUDIO = 1;
VIDEO = 2;
}
}
//it is just empty
message LinkerCloseContent {
}
message LinkerCreateContent {
int64 OwnerId = 1;
int64 OwnerRoomId = 2;
int64 LinkType = 3;
int64 ownerId = 1;
int64 ownerRoomId = 2;
int64 linkType = 3;
}
message LinkerEnterContent {
repeated User LinkedUsersList = 1;
// LinkmicMultiLiveEnum AnchorMultiLiveEnum = 2;
// Data.LinkmicUserSettingInfo AnchorSettingInfo = 3;
repeated ListUser linkedUsersList = 1;
int32 anchorMultiLiveEnum = 2; // Enum
LinkmicUserSettingInfo anchorSettingInfo = 3;;
}
message LinkerInviteContent {
int64 FromUserId = 1;
int64 FromRoomId = 2;
string ToRtcExtInfo = 3;
bool RtcJoinChannel = 4;
int64 Vendor = 5;
string SecFromUserId = 6;
string ToLinkmicIdStr = 7;
User FromUser = 8;
int64 RequiredMicIdx = 9;
map<int64, string> RtcExtInfoMap = 10;
//Data.LinkmicMultiLiveEnum AnchorMultiLiveEnum = 11;
//Data.LinkmicUserSettingInfo AnchorSettingInfo = 12;
string InviterLinkmicIdStr = 13;
// InviteTopHostInfo FromTopHostInfo = 16;
int64 ActionId = 17;
// repeated LinkmicUserInfo LinkedUsersList = 18;
// Data.PerceptionDialogInfo Dialog = 19;
// Data.PunishEventInfo PunishInfo = 20;
int32 FromRoomAgeRestricted = 21;
// Data.Tag FromTag = 22;
// repeated Data.CohostABTestSetting AbTestSettingList = 23;
// Data.LinkerInviteMessageExtra LinkerInviteMsgExtra = 101;
int64 fromUserId = 1;
int64 fromRoomId = 2;
string toRtcExtInfo = 3;
bool rtcJoinChannel = 4;
int64 vendor = 5;
string secFromUserId = 6;
string toLinkmicIdStr = 7;
User fromUser = 8;
int64 requiredMicIdx = 9;
}
message LinkerKickOutContent {
int64 FromUserId = 1;
//LinkMic.KickoutReason KickoutReason = 2;
int64 fromUserId = 1;
KickoutReason kickoutReason = 2; // Enum
}
message LinkerLeaveContent {
int64 UserId = 1;
string LinkmicIdStr = 2;
int64 SendLeaveUid = 3;
int64 LeaveReason = 4;
int64 userId = 1;
string linkmicIdStr = 2;
int64 sendLeaveUid = 3;
int64 leaveReason = 4;
}
//Empty
message LinkerLinkedListChangeContent {
repeated User LinkedUsersList = 1;
}
//Empty
message CohostListChangeContent {
}
message LinkerListChangeContent {
repeated LinkLayerListUser LinkedUsersList = 1;
repeated LinkLayerListUser AppliedUsersList = 2;
repeated LinkLayerListUser ConnectingUsersList = 3;
repeated ListUser linkedUsersList = 1;
repeated ListUser appliedUsersList = 2;
repeated ListUser connectingUsersList = 3;
}
message LinkerMediaChangeContent {
// MicIdxOperation Op = 1;
int64 ToUserId = 2;
int64 AnchorId = 3;
int64 RoomId = 4;
// LinkerSceneType ChangeScene = 5;
int64 op = 1; // Enum
int64 toUserId = 2;
int64 anchorId = 3;
int64 roomId = 4;
int64 changeScene = 5; // Enum
}
//Empty
message LinkerMicIdxUpdateContent {
LinkerMicIdxUpdateInfo MicIdxUpdateInfo = 1;
}
message LinkerMicIdxUpdateInfo {
// MicIdxOperation Op = 1;
int64 UserId = 2;
int64 MicIdx = 3;
}
message LinkerMuteContent {
int64 UserId = 1;
// Data.MuteStatus Status = 2;
int64 userId = 1;
int64 status = 2; // Enum
}
message LinkerRandomMatchContent {
User User = 1;
int64 RoomId = 2;
int64 InviteType = 3;
string MatchId = 4;
int64 InnerChannelId = 5;
User user = 1;
int64 roomId = 2;
int64 inviteType = 3;
string matchId = 4;
int64 innerChannelId = 5;
}
message LinkerReplyContent {
int64 FromUserId = 1;
int64 FromRoomId = 2;
// LinkmicInfo FromUserLinkmicInfo = 3;
int64 ToUserId = 4;
// LinkmicInfo ToUserLinkmicInfo = 5;
int64 LinkType = 6;
int64 ReplyStatus = 7;
LinkerSetting LinkerSetting = 8;
User FromUser = 9;
User ToUser = 10;
map<int64, string> RtcExtInfoMap = 11;
LinkerMicIdxUpdateInfo InviteeMicIdxUpdateInfo = 12;
map<int64, int64> ApplierMicIdxInfoMap = 13;
// Data.LinkmicMultiLiveEnum AnchorMultiLiveEnum = 14;
// Data.LinkmicUserSettingInfo AnchorSettingInfo = 15;
int64 ActionId = 16;
// repeated LinkmicUserInfo LinkedUsersList = 17;
int64 SourceType = 18;
int64 fromUserId = 1;
int64 fromRoomId = 2;
LinkmicInfo fromUserLinkmicInfo = 3;
int64 toUserId = 4;
LinkmicInfo toUserLinkmicInfo = 5;
int64 linkType = 6;
int64 replyStatus = 7;
LinkerSetting linkerSetting = 8;
User fromUser = 9;
User toUser = 10;
message LinkmicInfo {
string accessKey = 1;
int64 linkMicId = 2;
bool joinable = 3;
int32 confluenceType = 4;
string rtcExtInfo = 5;
string rtcAppId = 6;
string rtcAppSign = 7;
string linkmicIdStr = 8;
int64 vendor = 9;
}
}
message LinkerSetting {
@@ -1113,20 +1128,27 @@ message LinkerSetting {
}
message LinkerSysKickOutContent {
int64 UserId = 1;
string LinkmicIdStr = 2;
int64 userId = 1;
string linkmicIdStr = 2;
}
message LinkmicUserToastContent {
int64 userId = 1;
int64 roomId = 2;
Text displayText = 3;
}
message LinkerUpdateUserContent {
int64 FromUserId = 1;
int64 ToUserId = 2;
map<string, string> UpdateInfoMap = 3;
int64 fromUserId = 1;
int64 toUserId = 2;
}
//Empty
message LinkerUpdateUserSettingContent {
// Data.LinkmicUserSettingInfo UpdateUserSettingInfo = 1;
}
//Empty
message LinkerWaitingListChangeContent {
}

View File

@@ -241,6 +241,35 @@ enum CommonContentCase {
}
enum LinkMessageType {
TPYE_LINKER_UNKNOWN = 0;
TYPE_LINKER_CREATE = 1;
TYPE_LINKER_CLOSE = 2;
TYPE_LINKER_INVITE = 3;
TYPE_LINKER_APPLY = 4;
TYPE_LINKER_REPLY = 5;
TPYE_LINKER_ENTER = 6;
TPYE_LINKER_LEAVE = 7;
TYPE_LINKER_PERMIT = 8;
TPYE_LINKER_CANCEL_INVITE = 9;
TYPE_LINKER_WAITING_LIST_CHANGE = 10;
TYPE_LINKER_LINKED_LIST_CHANGE = 11;
TYPE_LINKER_UPDATE_USER = 12;
TPYE_LINKER_KICK_OUT = 13;
TPYE_LINKER_CANCEL_APPLY = 14;
TYPE_LINKER_MUTE = 15;
TYPE_LINKER_MATCH = 16;
TYPE_LINKER_UPDATE_USER_SETTING = 17;
TYPE_LINKER_MIC_IDX_UPDATE = 18;
TYPE_LINKER_LEAVE_V2 = 19;
TYPE_LINKER_WAITING_LIST_CHANGE_V2 = 20;
TYPE_LINKER_LINKED_LIST_CHANGE_V2 = 21;
TYPE_LINKER_COHOST_LIST_CHANGE = 22;
TYPE_LINKER_MEDIA_CHANGE = 23;
TYPE_LINKER_ACCEPT_NOTICE = 24;
TPYE_LINKER_SYS_KICK_OUT = 101;
TPYE_LINKMIC_USER_TOAST = 102;
}
enum MessageType {
MESSAGETYPE_SUBSUCCESS = 0;

View File

@@ -25,10 +25,6 @@ message WebcastPushFrame {
}
message WebcastWebsocketAck {
uint64 Id = 1;
string Type = 2;
}
//@WebcastResponse
@@ -733,7 +729,7 @@ message WebcastSystemMessage {
//@WebcastLinkMessage
message WebcastLinkMessage {
Common common = 1;
MessageType MessageType = 2;
LinkMessageType MessageType = 2;
int64 LinkerId = 3;
Scene Scene = 4;
LinkerInviteContent InviteContent = 5;
@@ -752,14 +748,14 @@ message WebcastLinkMessage {
LinkerUpdateUserSettingContent UpdateUserSettingContent = 18;
LinkerMicIdxUpdateContent MicIdxUpdateContent = 19;
LinkerListChangeContent ListChangeContent = 20;
// CohostListChangeContent CohostListChangeContent = 21;
CohostListChangeContent CohostListChangeContent = 21;
LinkerMediaChangeContent MediaChangeContent = 22;
LinkerAcceptNoticeContent ReplyAcceptNoticeContent = 23;
LinkerSysKickOutContent SysKickOutContent = 101;
// LinkmicUserToastContent UserToastContent = 102;
string Extra = 200;
int64 ExpireTimestamp = 201;
string TransferExtra = 202;
LinkmicUserToastContent UserToastContent = 102;
string extra = 200;
int64 expireTimestamp = 201;
string transferExtra = 202;
}

View File

@@ -5,7 +5,7 @@
<parent>
<artifactId>TikTokLiveJava</artifactId>
<groupId>io.github.jwdeveloper.tiktok</groupId>
<version>1.0.11-Release</version>
<version>1.1.0-Release</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -39,7 +39,7 @@
<dependency>
<groupId>org.java-websocket</groupId>
<artifactId>Java-WebSocket</artifactId>
<version>1.5.4</version>
<version>1.5.5</version>
</dependency>
<dependency>
<groupId>org.testng</groupId>

View File

@@ -23,26 +23,22 @@
package io.github.jwdeveloper.tiktok;
import io.github.jwdeveloper.tiktok.http.TikTokDataChecker;
import io.github.jwdeveloper.tiktok.http.LiveHttpClient;
import io.github.jwdeveloper.tiktok.live.builder.LiveClientBuilder;
import java.util.concurrent.CompletableFuture;
public class TikTokLive
{
public class TikTokLive {
/**
*
* @param hostName profile name of Tiktok user could be found in profile link
* example: https://www.tiktok.com/@dostawcavideo hostName would be dostawcavideo
* example: https://www.tiktok.com/@dostawcavideo hostName would be dostawcavideo
* @return LiveClientBuilder
*/
public static LiveClientBuilder newClient(String hostName)
{
public static LiveClientBuilder newClient(String hostName) {
return new TikTokLiveClientBuilder(hostName);
}
/**
*
* @param hostName profile name of Tiktok user could be found in profile link
@@ -51,10 +47,9 @@ public class TikTokLive
*/
public static boolean isLiveOnline(String hostName)
{
return new TikTokDataChecker().isOnline(hostName);
return requests().fetchLiveUserData(hostName).isLiveOnline();
}
/**
*
* @param hostName profile name of Tiktok user could be found in profile link
@@ -63,7 +58,7 @@ public class TikTokLive
*/
public static CompletableFuture<Boolean> isLiveOnlineAsync(String hostName)
{
return new TikTokDataChecker().isOnlineAsync(hostName);
return CompletableFuture.supplyAsync(()-> isLiveOnline(hostName));
}
/**
@@ -74,7 +69,7 @@ public class TikTokLive
*/
public static boolean isHostNameValid(String hostName)
{
return new TikTokDataChecker().isHostNameValid(hostName);
return requests().fetchLiveUserData(hostName).isHostNameValid();
}
/**
@@ -85,7 +80,15 @@ public class TikTokLive
*/
public static CompletableFuture<Boolean> isHostNameValidAsync(String hostName)
{
return new TikTokDataChecker().isHostNameValidAsync(hostName);
return CompletableFuture.supplyAsync(()-> isHostNameValid(hostName));
}
}
/**
* Use to get some data from TikTok about users are lives
*
* @return LiveHttpClient
*/
public static LiveHttpClient requests() {
return new TikTokLiveHttpClient();
}
}

View File

@@ -26,19 +26,22 @@ import io.github.jwdeveloper.tiktok.data.events.TikTokDisconnectedEvent;
import io.github.jwdeveloper.tiktok.data.events.TikTokErrorEvent;
import io.github.jwdeveloper.tiktok.data.events.TikTokReconnectingEvent;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent;
import io.github.jwdeveloper.tiktok.data.events.control.TikTokConnectingEvent;
import io.github.jwdeveloper.tiktok.data.events.http.TikTokRoomDataResponseEvent;
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.gifts.TikTokGiftManager;
import io.github.jwdeveloper.tiktok.handlers.TikTokEventObserver;
import io.github.jwdeveloper.tiktok.http.TikTokApiService;
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.LiveClient;
import io.github.jwdeveloper.tiktok.live.LiveRoomInfo;
import io.github.jwdeveloper.tiktok.live.LiveRoomMeta;
import io.github.jwdeveloper.tiktok.models.ConnectionState;
import io.github.jwdeveloper.tiktok.data.settings.LiveClientSettings;
import io.github.jwdeveloper.tiktok.websocket.SocketClient;
import java.util.concurrent.CompletableFuture;
@@ -48,24 +51,24 @@ import java.util.logging.Logger;
public class TikTokLiveClient implements LiveClient {
private final TikTokRoomInfo liveRoomInfo;
private final TikTokGiftManager tikTokGiftManager;
private final TikTokApiService apiService;
private final TikTokLiveHttpClient httpClient;
private final SocketClient webSocketClient;
private final TikTokEventObserver tikTokEventHandler;
private final ClientSettings clientSettings;
private final TikTokLiveEventHandler tikTokEventHandler;
private final LiveClientSettings clientSettings;
private final TikTokListenersManager listenersManager;
private final Logger logger;
public TikTokLiveClient(TikTokRoomInfo tikTokLiveMeta,
TikTokApiService tikTokApiService,
TikTokLiveHttpClient tiktokHttpClient,
SocketClient webSocketClient,
TikTokGiftManager tikTokGiftManager,
TikTokEventObserver tikTokEventHandler,
ClientSettings clientSettings,
TikTokLiveEventHandler tikTokEventHandler,
LiveClientSettings clientSettings,
TikTokListenersManager listenersManager,
Logger logger) {
this.liveRoomInfo = tikTokLiveMeta;
this.tikTokGiftManager = tikTokGiftManager;
this.apiService = tikTokApiService;
this.httpClient = tiktokHttpClient;
this.webSocketClient = webSocketClient;
this.tikTokEventHandler = tikTokEventHandler;
this.clientSettings = clientSettings;
@@ -75,17 +78,15 @@ public class TikTokLiveClient implements LiveClient {
public void connectAsync(Consumer<LiveClient> onConnection) {
CompletableFuture.supplyAsync(() ->
{
CompletableFuture.runAsync(() -> {
connect();
onConnection.accept(this);
return this;
});
}
public CompletableFuture<LiveClient> connectAsync() {
return CompletableFuture.supplyAsync(() ->
{
return CompletableFuture.supplyAsync(() -> {
connect();
return this;
});
@@ -102,8 +103,7 @@ 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();
@@ -116,54 +116,64 @@ public class TikTokLiveClient implements LiveClient {
}
}
public void disconnect() {
if (liveRoomInfo.hasConnectionState(ConnectionState.DISCONNECTED)) {
return;
}
webSocketClient.stop();
setState(ConnectionState.DISCONNECTED);
}
public void tryConnect() {
if (liveRoomInfo.hasConnectionState(ConnectionState.CONNECTED))
if (!liveRoomInfo.hasConnectionState(ConnectionState.DISCONNECTED)) {
throw new TikTokLiveException("Already connected");
if (liveRoomInfo.hasConnectionState(ConnectionState.CONNECTING))
throw new TikTokLiveException("Already connecting");
}
setState(ConnectionState.CONNECTING);
apiService.updateSessionId();
if (clientSettings.getRoomId() != null) {
liveRoomInfo.setRoomId(clientSettings.getRoomId());
logger.info("Using roomID from settings: " + clientSettings.getRoomId());
} else {
var roomId = apiService.fetchRoomId(liveRoomInfo.getHostName());
liveRoomInfo.setRoomId(roomId);
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.getHostUser());
}
if (userData.getUserStatus() == LiveUserData.UserStatus.NotFound) {
throw new TikTokLiveOfflineHostException("User not found: "+liveRoomInfo.getHostUser());
}
var liveRoomMeta = apiService.fetchRoomInfo();
if (liveRoomMeta.getStatus() == LiveRoomMeta.LiveRoomStatus.HostNotFound) {
var liveDataRequest = new LiveData.Request(userData.getRoomId());
var liveData = httpClient.fetchLiveData(liveDataRequest);
tikTokEventHandler.publish(this, new TikTokRoomDataResponseEvent(liveData));
if (liveData.getLiveStatus() == LiveData.LiveStatus.HostNotFound) {
throw new TikTokLiveOfflineHostException("LiveStream for Host name could not be found.");
}
if (liveRoomMeta.getStatus() == LiveRoomMeta.LiveRoomStatus.HostOffline) {
if (liveData.getLiveStatus() == LiveData.LiveStatus.HostOffline) {
throw new TikTokLiveOfflineHostException("LiveStream for not be found, is the Host offline?");
}
liveRoomInfo.setTitle(liveRoomMeta.getTitie());
liveRoomInfo.setViewersCount(liveRoomMeta.getViewers());
liveRoomInfo.setTotalViewersCount(liveRoomMeta.getTotalViewers());
liveRoomInfo.setAgeRestricted(liveRoomMeta.isAgeRestricted());
liveRoomInfo.setHost(liveRoomMeta.getHost());
liveRoomInfo.setTitle(liveData.getTitle());
liveRoomInfo.setViewersCount(liveData.getViewers());
liveRoomInfo.setTotalViewersCount(liveData.getTotalViewers());
liveRoomInfo.setAgeRestricted(liveData.isAgeRestricted());
liveRoomInfo.setHost(liveData.getHost());
var liveConnectionRequest =new LiveConnectionData.Request(userData.getRoomId());
var liveConnectionData = httpClient.fetchLiveConnectionData(liveConnectionRequest);
webSocketClient.start(liveConnectionData, this);
var clientData = apiService.fetchClientData();
webSocketClient.start(clientData, this);
setState(ConnectionState.CONNECTED);
tikTokEventHandler.publish(this, new TikTokRoomInfoEvent(liveRoomInfo));
}
public void disconnect() {
if (liveRoomInfo.hasConnectionState(ConnectionState.DISCONNECTED)) {
return;
}
setState(ConnectionState.DISCONNECTED);
webSocketClient.stop();
}
private void setState(ConnectionState connectionState) {
logger.info("TikTokLive client state: " + connectionState.name());
liveRoomInfo.setConnectionState(connectionState);
}
public void publishEvent(TikTokEvent event) {
tikTokEventHandler.publish(this, event);
}
public LiveRoomInfo getRoomInfo() {
return liveRoomInfo;
@@ -183,15 +193,4 @@ public class TikTokLiveClient implements LiveClient {
public GiftManager getGiftManager() {
return tikTokGiftManager;
}
private void setState(ConnectionState connectionState) {
logger.info("TikTokLive client state: " + connectionState.name());
liveRoomInfo.setConnectionState(connectionState);
}
public void publishEvent(TikTokEvent event)
{
tikTokEventHandler.publish(this, event);
}
}
}

View File

@@ -40,12 +40,7 @@ import io.github.jwdeveloper.tiktok.data.events.websocket.TikTokWebsocketRespons
import io.github.jwdeveloper.tiktok.data.events.websocket.TikTokWebsocketUnhandledMessageEvent;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveException;
import io.github.jwdeveloper.tiktok.gifts.TikTokGiftManager;
import io.github.jwdeveloper.tiktok.handlers.TikTokEventObserver;
import io.github.jwdeveloper.tiktok.handlers.TikTokMessageHandler;
import io.github.jwdeveloper.tiktok.http.TikTokApiService;
import io.github.jwdeveloper.tiktok.http.TikTokCookieJar;
import io.github.jwdeveloper.tiktok.http.TikTokHttpClient;
import io.github.jwdeveloper.tiktok.http.TikTokHttpRequestFactory;
import io.github.jwdeveloper.tiktok.http.HttpClientFactory;
import io.github.jwdeveloper.tiktok.listener.TikTokEventListener;
import io.github.jwdeveloper.tiktok.listener.TikTokListenersManager;
import io.github.jwdeveloper.tiktok.live.GiftManager;
@@ -62,10 +57,10 @@ import io.github.jwdeveloper.tiktok.mappers.handlers.TikTokGiftEventHandler;
import io.github.jwdeveloper.tiktok.mappers.handlers.TikTokRoomInfoEventHandler;
import io.github.jwdeveloper.tiktok.mappers.handlers.TikTokSocialMediaEventHandler;
import io.github.jwdeveloper.tiktok.messages.webcast.*;
import io.github.jwdeveloper.tiktok.data.settings.LiveClientSettings;
import io.github.jwdeveloper.tiktok.utils.ConsoleColors;
import io.github.jwdeveloper.tiktok.websocket.TikTokWebSocketClient;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@@ -74,17 +69,17 @@ import java.util.logging.*;
public class TikTokLiveClientBuilder implements LiveClientBuilder {
protected final ClientSettings clientSettings;
protected final LiveClientSettings clientSettings;
protected final Logger logger;
protected final TikTokEventObserver tikTokEventHandler;
protected final TikTokLiveEventHandler tikTokEventHandler;
protected final List<TikTokEventListener> listeners;
protected Consumer<TikTokMapper> onCustomMappings;
public TikTokLiveClientBuilder(String userName) {
this.tikTokEventHandler = new TikTokEventObserver();
this.clientSettings = Constants.DefaultClientSettings();
public TikTokLiveClientBuilder(String userName)
{
this.clientSettings = LiveClientSettings.createDefault();
this.clientSettings.setHostName(userName);
this.tikTokEventHandler = new TikTokLiveEventHandler();
this.logger = Logger.getLogger(TikTokLive.class.getSimpleName() + " " + userName);
this.listeners = new ArrayList<>();
this.onCustomMappings = (e) -> {
@@ -97,7 +92,7 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder {
}
public TikTokLiveClientBuilder configure(Consumer<ClientSettings> onConfigure) {
public TikTokLiveClientBuilder configure(Consumer<LiveClientSettings> onConfigure) {
onConfigure.accept(clientSettings);
return this;
}
@@ -108,17 +103,12 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder {
}
protected void validate() {
if (clientSettings.getTimeout() == null) {
clientSettings.setTimeout(Duration.ofSeconds(Constants.DEFAULT_TIMEOUT));
}
if (clientSettings.getClientLanguage() == null || clientSettings.getClientLanguage().equals("")) {
clientSettings.setClientLanguage(Constants.DefaultClientSettings().getClientLanguage());
if (clientSettings.getClientLanguage() == null || clientSettings.getClientLanguage().isEmpty()) {
clientSettings.setClientLanguage("en");
}
if (clientSettings.getHostName() == null || clientSettings.getHostName().equals("")) {
if (clientSettings.getHostName() == null || clientSettings.getHostName().isEmpty()) {
throw new TikTokLiveException("HostName can not be null");
}
@@ -127,9 +117,9 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder {
}
var params = clientSettings.getClientParameters();
params.put("app_language", clientSettings.getClientLanguage());
params.put("webcast_language", clientSettings.getClientLanguage());
var httpSettings = clientSettings.getHttpSettings();
httpSettings.getParams().put("app_language", clientSettings.getClientLanguage());
httpSettings.getParams().put("webcast_language", clientSettings.getClientLanguage());
var handler = new ConsoleHandler();
@@ -146,9 +136,7 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder {
});
logger.setUseParentHandlers(false);
logger.addHandler(handler);
logger.setLevel(clientSettings.getLogLevel());
if (!clientSettings.isPrintToConsole()) {
logger.setLevel(Level.OFF);
}
@@ -161,23 +149,22 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder {
tiktokRoomInfo.setHostName(clientSettings.getHostName());
var listenerManager = new TikTokListenersManager(listeners, tikTokEventHandler);
var cookieJar = new TikTokCookieJar();
var requestFactory = new TikTokHttpRequestFactory(cookieJar, tikTokEventHandler);
var apiClient = new TikTokHttpClient(cookieJar, requestFactory);
var apiService = new TikTokApiService(apiClient, logger, clientSettings);
var giftManager = new TikTokGiftManager(logger);
var eventsMapper = createMapper(giftManager, tiktokRoomInfo);
var messageHandler = new TikTokMessageHandler(tikTokEventHandler, eventsMapper);
var messageHandler = new TikTokLiveMessageHandler(tikTokEventHandler, eventsMapper);
var httpClientFactory = new HttpClientFactory(clientSettings);
var tikTokLiveHttpClient = new TikTokLiveHttpClient(httpClientFactory, clientSettings);
var webSocketClient = new TikTokWebSocketClient(
cookieJar,
clientSettings,
messageHandler,
tikTokEventHandler);
return new TikTokLiveClient(tiktokRoomInfo,
apiService,
tikTokLiveHttpClient,
webSocketClient,
giftManager,
tikTokEventHandler,
@@ -187,6 +174,11 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder {
}
public TikTokLiveMapper createMapper(GiftManager giftManager, TikTokRoomInfo roomInfo) {
/*
//
*/
var eventMapper = new TikTokGenericEventMapper();
var mapper = new TikTokLiveMapper(new TikTokLiveMapperHelper(eventMapper));
@@ -248,14 +240,14 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder {
//LinkMic events
// mapper.webcastObjectToConstructor(WebcastLinkMicBattle.class, TikTokLinkMicBattleEvent.class);
// mapper.webcastObjectToConstructor(WebcastLinkMicBattle.class, TikTokLinkMicBattleEvent.class);
// mapper.webcastObjectToConstructor(WebcastLinkMicArmies.class, TikTokLinkMicArmiesEvent.class);
// mapper.webcastObjectToConstructor(WebcastLinkMicMethod.class, TikTokLinkMicMethodEvent.class);
// mapper.webcastObjectToConstructor(WebcastLinkMicFanTicketMethod.class, TikTokLinkMicFanTicketEvent.class);
// mapper.webcastObjectToConstructor(WebcastLinkMicFanTicketMethod.class, TikTokLinkMicFanTicketEvent.class);
//Rank events
// mapper.webcastObjectToConstructor(WebcastRankTextMessage.class, TikTokRankTextEvent.class);
// mapper.webcastObjectToConstructor(WebcastRankUpdateMessage.class, TikTokRankUpdateEvent.class);
// mapper.webcastObjectToConstructor(WebcastRankUpdateMessage.class, TikTokRankUpdateEvent.class);
// mapper.webcastObjectToConstructor(WebcastHourlyRankMessage.class, TikTokRankUpdateEvent.class);
//Others events
@@ -263,7 +255,7 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder {
// mapper.webcastObjectToConstructor(WebcastMsgDetectMessage.class, TikTokDetectEvent.class);
// mapper.webcastObjectToConstructor(WebcastBarrageMessage.class, TikTokBarrageEvent.class);
// mapper.webcastObjectToConstructor(WebcastUnauthorizedMemberMessage.class, TikTokUnauthorizedMemberEvent.class);
// mapper.webcastObjectToConstructor(WebcastOecLiveShoppingMessage.class, TikTokShopEvent.class);
// mapper.webcastObjectToConstructor(WebcastOecLiveShoppingMessage.class, TikTokShopEvent.class);
// mapper.webcastObjectToConstructor(WebcastImDeleteMessage.class, TikTokIMDeleteEvent.class);
// mapper.bytesToEvents(WebcastEnvelopeMessage.class, commonHandler::handleEnvelop);
@@ -354,7 +346,7 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder {
@Override
public LiveClientBuilder onRoomInfo(EventConsumer<TikTokRoomInfoEvent> event) {
public TikTokLiveClientBuilder onRoomInfo(EventConsumer<TikTokRoomInfoEvent> event) {
tikTokEventHandler.subscribe(TikTokRoomInfoEvent.class, event);
return this;
}
@@ -366,7 +358,7 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder {
}
@Override
public LiveClientBuilder onLiveUnpaused(EventConsumer<TikTokLiveUnpausedEvent> event) {
public TikTokLiveClientBuilder onLiveUnpaused(EventConsumer<TikTokLiveUnpausedEvent> event) {
tikTokEventHandler.subscribe(TikTokLiveUnpausedEvent.class, event);
return this;
}
@@ -552,11 +544,4 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder {
tikTokEventHandler.subscribe(TikTokReconnectingEvent.class, event);
return this;
}
}
}

View File

@@ -20,7 +20,7 @@
* 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.handlers;
package io.github.jwdeveloper.tiktok;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent;
import io.github.jwdeveloper.tiktok.live.builder.EventConsumer;
@@ -31,10 +31,10 @@ import java.util.HashSet;
import java.util.Map;
import java.util.Set;
public class TikTokEventObserver {
public class TikTokLiveEventHandler {
private final Map<Class<?>, Set<EventConsumer>> events;
public TikTokEventObserver() {
public TikTokLiveEventHandler() {
events = new HashMap<>();
}

View File

@@ -0,0 +1,247 @@
/*
* 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;
import com.google.protobuf.InvalidProtocolBufferException;
import io.github.jwdeveloper.tiktok.data.requests.*;
import io.github.jwdeveloper.tiktok.data.settings.*;
import io.github.jwdeveloper.tiktok.exceptions.*;
import io.github.jwdeveloper.tiktok.http.*;
import io.github.jwdeveloper.tiktok.http.mappers.*;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastResponse;
import java.net.http.HttpResponse;
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";
private static final String TIKTOK_URL_WEB = "https://www.tiktok.com/";
private static final String TIKTOK_URL_WEBCAST = "https://webcast.tiktok.com/webcast/";
private final HttpClientFactory httpFactory;
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) {
this.httpFactory = factory;
clientSettings = settings;
liveUserDataMapper = new LiveUserDataMapper();
liveDataMapper = new LiveDataMapper();
signServerResponseMapper = new SignServerResponseMapper();
giftsDataMapper = new GiftsDataMapper();
}
public TikTokLiveHttpClient() {
this(new HttpClientFactory(LiveClientSettings.createDefault()), LiveClientSettings.createDefault());
}
public GiftsData.Response fetchGiftsData() {
var url = TIKTOK_URL_WEBCAST + "gift/list/";
var optional = httpFactory.client(url)
.build()
.toJsonResponse();
if (optional.isEmpty()) {
throw new TikTokLiveRequestException("Unable to fetch gifts information's");
}
var json = optional.get();
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 proxyClientSettings = clientSettings.getHttpSettings().getProxyClientSettings();
if (proxyClientSettings.isEnabled()) {
while (proxyClientSettings.hasNext()) {
try {
var url = TIKTOK_URL_WEB + "api-live/user/room";
var optional = httpFactory.client(url)
.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) {}
}
}
var url = TIKTOK_URL_WEB + "api-live/user/room";
var optional = httpFactory.client(url)
.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);
}
@Override
public LiveData.Response fetchLiveData(String roomId) {
return fetchLiveData(new LiveData.Request(roomId));
}
@Override
public LiveData.Response fetchLiveData(LiveData.Request request) {
var proxyClientSettings = clientSettings.getHttpSettings().getProxyClientSettings();
if (proxyClientSettings.isEnabled()) {
while (proxyClientSettings.hasNext()) {
try {
var url = TIKTOK_URL_WEBCAST + "room/info";
var optional = httpFactory.client(url)
.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) {}
}
}
var url = TIKTOK_URL_WEBCAST + "room/info";
var optional = httpFactory.client(url)
.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);
}
@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());
});
try {
var optionalHeader = credentialsResponse.headers().firstValue("set-cookie");
if (optionalHeader.isEmpty()) {
throw new TikTokSignServerException("Sign server did not return the set-cookie header");
}
var websocketCookie = optionalHeader.get();
var webcastResponse = WebcastResponse.parseFrom(credentialsResponse.body());
var webSocketUrl = httpFactory
.client(webcastResponse.getPushServer())
.withParam("room_id", request.getRoomId())
.withParam("cursor", webcastResponse.getCursor())
.withParam("resp_content_type", "protobuf")
.withParam("internal_ext", webcastResponse.getInternalExt())
.withParams(webcastResponse.getRouteParamsMapMap())
.build()
.toUrl();
return new LiveConnectionData.Response(websocketCookie, webSocketUrl, webcastResponse);
} catch (InvalidProtocolBufferException e) {
throw new TikTokSignServerException("Unable to parse websocket credentials response to WebcastResponse");
}
}
SignServerResponse getSignedUrl(String roomId) {
var urlToSign = httpFactory
.client(TikTokLiveHttpClient.TIKTOK_URL_WEBCAST + "im/fetch")
.withParam("room_id", roomId)
.build()
.toUrl();
var optional = httpFactory
.client(TikTokLiveHttpClient.TIKTOK_SIGN_API)
.withParam("client", "ttlive-java")
.withParam("uuc", "1")
.withParam("url", urlToSign.toString())
.build()
.toJsonResponse();
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();
}
Optional<HttpResponse<byte[]>> getOptionalProxyResponse(LiveConnectionData.Request request) {
var proxyClientSettings = clientSettings.getHttpSettings().getProxyClientSettings();
if (proxyClientSettings.isEnabled()) {
while (proxyClientSettings.hasNext()) {
try {
SignServerResponse signServerResponse = getSignedUrl(request.getRoomId());
HttpResponse<byte[]> credentialsResponse = getWebsocketCredentialsResponse(signServerResponse.getSignedUrl());
clientSettings.getHttpSettings().getProxyClientSettings().rotate();
return Optional.of(credentialsResponse);
} catch (TikTokProxyRequestException | TikTokSignServerException ignored) {}
}
}
return Optional.empty();
}
}

View File

@@ -20,7 +20,7 @@
* 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.handlers;
package io.github.jwdeveloper.tiktok;
import io.github.jwdeveloper.tiktok.data.dto.MessageMetaData;
@@ -36,18 +36,16 @@ import io.github.jwdeveloper.tiktok.utils.Stopwatch;
import java.time.Duration;
public class TikTokLiveMessageHandler {
public class TikTokMessageHandler {
private final TikTokEventObserver tikTokEventHandler;
private final TikTokLiveEventHandler tikTokEventHandler;
private final TikTokLiveMapper mapper;
public TikTokMessageHandler(TikTokEventObserver tikTokEventHandler, TikTokLiveMapper mapper) {
public TikTokLiveMessageHandler(TikTokLiveEventHandler tikTokEventHandler, TikTokLiveMapper mapper) {
this.tikTokEventHandler = tikTokEventHandler;
this.mapper = mapper;
}
public void handle(LiveClient client, WebcastResponse webcastResponse) {
tikTokEventHandler.publish(client, new TikTokWebsocketResponseEvent(webcastResponse));
for (var message : webcastResponse.getMessagesList()) {
@@ -60,7 +58,8 @@ public class TikTokMessageHandler {
}
}
public void handleSingleMessage(LiveClient client, WebcastResponse.Message message) throws Exception {
public void handleSingleMessage(LiveClient client, WebcastResponse.Message message)
{
var messageClassName = message.getMethod();
if (!mapper.isRegistered(messageClassName)) {
tikTokEventHandler.publish(client, new TikTokWebsocketUnhandledMessageEvent(message));

View File

@@ -22,11 +22,10 @@
*/
package io.github.jwdeveloper.tiktok;
import io.github.jwdeveloper.tiktok.data.models.Picture;
import io.github.jwdeveloper.tiktok.data.models.RankingUser;
import io.github.jwdeveloper.tiktok.data.models.users.User;
import io.github.jwdeveloper.tiktok.models.ConnectionState;
import io.github.jwdeveloper.tiktok.live.LiveRoomInfo;
import io.github.jwdeveloper.tiktok.models.ConnectionState;
import lombok.Data;
import java.util.LinkedList;
@@ -42,6 +41,8 @@ public class TikTokRoomInfo implements LiveRoomInfo {
private int totalViewersCount;
private long startTime;
private boolean ageRestricted;
private User host;
@@ -69,5 +70,4 @@ public class TikTokRoomInfo implements LiveRoomInfo {
usersRanking.clear();
usersRanking.addAll(rankingUsers);
}
}
}

View File

@@ -22,14 +22,12 @@
*/
package io.github.jwdeveloper.tiktok.gifts;
import io.github.jwdeveloper.tiktok.data.models.gifts.Gift;
import io.github.jwdeveloper.tiktok.data.models.Picture;
import io.github.jwdeveloper.tiktok.data.models.gifts.Gift;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveException;
import io.github.jwdeveloper.tiktok.live.GiftManager;
import sun.misc.Unsafe;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.logging.Logger;
@@ -89,23 +87,17 @@ public class TikTokGiftManager implements GiftManager {
}
public Gift findById(int giftId) {
if (!indexById.containsKey(giftId)) {
return Gift.UNDEFINED;
}
return indexById.get(giftId);
Gift gift = indexById.get(giftId);
return gift == null ? Gift.UNDEFINED : gift;
}
public Gift findByName(String giftName) {
if (!indexByName.containsKey(giftName)) {
return Gift.UNDEFINED;
}
return indexByName.get(giftName);
Gift gift = indexByName.get(giftName);
return gift == null ? Gift.UNDEFINED : gift;
}
@Override
public List<Gift> getGifts()
{
public List<Gift> getGifts() {
return indexById.values().stream().toList();
}
}
}

View File

@@ -0,0 +1,129 @@
/*
* 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;
import io.github.jwdeveloper.tiktok.data.settings.HttpClientSettings;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveRequestException;
import lombok.AllArgsConstructor;
import java.net.*;
import java.net.http.*;
import java.nio.charset.*;
import java.util.*;
import java.util.regex.*;
import java.util.stream.Collectors;
@AllArgsConstructor
public class HttpClient {
protected final HttpClientSettings httpClientSettings;
protected final String url;
private final Pattern pattern = Pattern.compile("charset=(.*?)(?=&|$)");
public Optional<HttpResponse<byte[]>> toResponse() {
var client = prepareClient();
var request = prepareGetRequest();
try {
var response = client.send(request, HttpResponse.BodyHandlers.ofByteArray());
if (response.statusCode() != 200) {
return Optional.empty();
}
return Optional.of(response);
} catch (Exception e) {
throw new TikTokLiveRequestException(e);
}
}
public Optional<String> toJsonResponse() {
var optional = toResponse();
if (optional.isEmpty()) {
return Optional.empty();
}
var response = optional.get();
var body = response.body();
var charset = charsetFrom(response.headers());
return Optional.of(new String(body,charset));
}
private Charset charsetFrom(HttpHeaders headers) {
String type = headers.firstValue("Content-type").orElse("text/html; charset=utf-8");
int i = type.indexOf(";");
if (i >= 0) type = type.substring(i+1);
try {
Matcher matcher = pattern.matcher(type);
if (!matcher.find())
return StandardCharsets.UTF_8;
return Charset.forName(matcher.group(1));
} catch (Throwable x) {
return StandardCharsets.UTF_8;
}
}
public Optional<byte[]> toBinaryResponse() {
var optional = toResponse();
if (optional.isEmpty()) {
return Optional.empty();
}
var body = optional.get().body();
return Optional.of(body);
}
public URI toUrl() {
var stringUrl = prepareUrlWithParameters(url, httpClientSettings.getParams());
return URI.create(stringUrl);
}
protected HttpRequest prepareGetRequest() {
var requestBuilder = HttpRequest.newBuilder().GET();
requestBuilder.uri(toUrl());
requestBuilder.timeout(httpClientSettings.getTimeout());
httpClientSettings.getHeaders().forEach(requestBuilder::setHeader);
httpClientSettings.getOnRequestCreating().accept(requestBuilder);
return requestBuilder.build();
}
protected java.net.http.HttpClient prepareClient() {
var builder = java.net.http.HttpClient.newBuilder()
.followRedirects(java.net.http.HttpClient.Redirect.NORMAL)
.cookieHandler(new CookieManager())
.connectTimeout(httpClientSettings.getTimeout());
httpClientSettings.getOnClientCreating().accept(builder);
return builder.build();
}
protected String prepareUrlWithParameters(String url, Map<String, Object> parameters) {
if (parameters.isEmpty()) {
return url;
}
return url + "?" + parameters.entrySet().stream().map(entry ->
{
var encodedKey = URLEncoder.encode(entry.getKey(), StandardCharsets.UTF_8);
var encodedValue = URLEncoder.encode(entry.getValue().toString(), StandardCharsets.UTF_8);
return encodedKey + "=" + encodedValue;
}).collect(Collectors.joining("&"));
}
}

View File

@@ -0,0 +1,85 @@
/*
* 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;
import io.github.jwdeveloper.tiktok.data.settings.HttpClientSettings;
import java.util.Map;
import java.util.function.Consumer;
public class HttpClientBuilder {
private final HttpClientSettings httpClientSettings;
private String url;
public HttpClientBuilder(String url, HttpClientSettings httpClientSettings) {
this.httpClientSettings = httpClientSettings;
this.url = url;
}
public HttpClientBuilder(String url) {
httpClientSettings = new HttpClientSettings();
this.url = url;
}
public HttpClientBuilder withUrl(String url) {
this.url = url;
return this;
}
public HttpClientBuilder withHttpClientSettings(Consumer<HttpClientSettings> consumer) {
consumer.accept(httpClientSettings);
return this;
}
public HttpClientBuilder withCookie(String name, String value) {
httpClientSettings.getCookies().put(name, value);
return this;
}
public HttpClientBuilder withHeader(String name, String value) {
httpClientSettings.getHeaders().put(name, value);
return this;
}
public HttpClientBuilder withParam(String name, String value) {
httpClientSettings.getParams().put(name, value);
return this;
}
public HttpClientBuilder withParams(Map<String, String> parameters) {
httpClientSettings.getParams().putAll(parameters);
return this;
}
public HttpClientBuilder withHeaders(Map<String, String> headers) {
httpClientSettings.getHeaders().putAll(headers);
return this;
}
public HttpClient build() {
if (httpClientSettings.getProxyClientSettings().isEnabled())
return new HttpProxyClient(httpClientSettings, url);
return new HttpClient(httpClientSettings, url);
}
}

View File

@@ -0,0 +1,44 @@
/*
* 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;
import io.github.jwdeveloper.tiktok.data.settings.*;
public class HttpClientFactory {
private final LiveClientSettings liveClientSettings;
public HttpClientFactory(LiveClientSettings liveClientSettings) {
this.liveClientSettings = liveClientSettings;
}
public HttpClientBuilder client(String url) {
return new HttpClientBuilder(url, liveClientSettings.getHttpSettings().clone());
}
//Does not contains default httpClientSettings, Params, headers, etd
public HttpClientBuilder clientEmpty(String url) {
var settings = new HttpClientSettings();
settings.setProxyClientSettings(liveClientSettings.getHttpSettings().getProxyClientSettings());
return new HttpClientBuilder(url, settings);
}
}

View File

@@ -0,0 +1,207 @@
/*
* 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;
import io.github.jwdeveloper.tiktok.data.settings.*;
import io.github.jwdeveloper.tiktok.exceptions.*;
import javax.net.ssl.*;
import java.io.IOException;
import java.net.*;
import java.net.http.*;
import java.net.http.HttpResponse.ResponseInfo;
import java.security.*;
import java.security.cert.X509Certificate;
import java.util.*;
import java.util.stream.Collectors;
public class HttpProxyClient extends HttpClient
{
private final ProxyClientSettings proxySettings;
public HttpProxyClient(HttpClientSettings httpClientSettings, String url) {
super(httpClientSettings, url);
this.proxySettings = httpClientSettings.getProxyClientSettings();
}
public Optional<HttpResponse<byte[]>> toResponse() {
return switch (proxySettings.getType()) {
case HTTP, DIRECT -> handleHttpProxyRequest();
default -> handleSocksProxyRequest();
};
}
public Optional<HttpResponse<byte[]>> handleHttpProxyRequest() {
var builder = java.net.http.HttpClient.newBuilder()
.followRedirects(java.net.http.HttpClient.Redirect.NORMAL)
.cookieHandler(new CookieManager())
.connectTimeout(httpClientSettings.getTimeout());
while (proxySettings.hasNext()) {
try {
InetSocketAddress address = proxySettings.next().toSocketAddress();
builder.proxy(ProxySelector.of(address));
httpClientSettings.getOnClientCreating().accept(builder);
var client = builder.build();
var request = prepareGetRequest();
var response = client.send(request, HttpResponse.BodyHandlers.ofByteArray());
if (response.statusCode() != 200) {
proxySettings.setLastSuccess(false);
continue;
}
proxySettings.setLastSuccess(true);
return Optional.of(response);
} catch (HttpConnectTimeoutException | ConnectException e) {
if (proxySettings.isAutoDiscard())
proxySettings.remove();
proxySettings.setLastSuccess(false);
} catch (Exception e) {
throw new TikTokLiveRequestException(e);
}
}
throw new TikTokLiveRequestException("No more proxies available!");
}
private Optional<HttpResponse<byte[]>> handleSocksProxyRequest() {
try {
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, new TrustManager[]{ new X509TrustManager() {
public void checkClientTrusted(X509Certificate[] x509Certificates, String s) {}
public void checkServerTrusted(X509Certificate[] x509Certificates, String s) {}
public X509Certificate[] getAcceptedIssuers() { return null; }
}}, null);
URL url = toUrl().toURL();
if (proxySettings.hasNext()) {
try {
Proxy proxy = new Proxy(Proxy.Type.SOCKS, proxySettings.next().toSocketAddress());
HttpsURLConnection socksConnection = (HttpsURLConnection) url.openConnection(proxy);
socksConnection.setSSLSocketFactory(sc.getSocketFactory());
socksConnection.setConnectTimeout(httpClientSettings.getTimeout().toMillisPart());
socksConnection.setReadTimeout(httpClientSettings.getTimeout().toMillisPart());
byte[] body = socksConnection.getInputStream().readAllBytes();
Map<String, List<String>> headers = socksConnection.getHeaderFields()
.entrySet()
.stream()
.filter(entry -> entry.getKey() != null)
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
var responseInfo = createResponseInfo(socksConnection.getResponseCode(), headers);
var response = createHttpResponse(body, toUrl(), responseInfo);
proxySettings.setLastSuccess(true);
return Optional.of(response);
} catch (IOException e) {
if (proxySettings.isAutoDiscard())
proxySettings.remove();
proxySettings.setLastSuccess(false);
throw new TikTokProxyRequestException(e);
} catch (Exception e) {
throw new TikTokLiveRequestException(e);
}
}
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!");
e.printStackTrace();
return Optional.empty();
} catch (TikTokLiveRequestException e) {
e.printStackTrace();
return Optional.empty();
}
}
private ResponseInfo createResponseInfo(int code, Map<String, List<String>> headers) {
return new ResponseInfo() {
@Override
public int statusCode() {
return code;
}
@Override
public HttpHeaders headers() {
return HttpHeaders.of(headers, (s, s1) -> s != null);
}
@Override
public java.net.http.HttpClient.Version version() {
return java.net.http.HttpClient.Version.HTTP_2;
}
};
}
private HttpResponse<byte[]> createHttpResponse(byte[] body,
URI uri,
ResponseInfo info) {
return new HttpResponse<>()
{
@Override
public int statusCode() {
return info.statusCode();
}
@Override
public HttpRequest request() {
throw new UnsupportedOperationException("TODO");
}
@Override
public Optional<HttpResponse<byte[]>> previousResponse() {
return Optional.empty();
}
@Override
public HttpHeaders headers() {
return info.headers();
}
@Override
public byte[] body() {
return body;
}
@Override
public Optional<SSLSession> sslSession() {
throw new UnsupportedOperationException("TODO");
}
@Override
public URI uri() {
return uri;
}
@Override
public java.net.http.HttpClient.Version version() {
return info.version();
}
};
}
}

View File

@@ -1,79 +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;
import lombok.SneakyThrows;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Map;
public class HttpUtils
{
public static String parseParameters(String url, Map<String,Object> parameters)
{
var parameterString = "";
if (!parameters.isEmpty()) {
var builder = new StringBuilder();
builder.append("?");
var first = false;
for (var param : parameters.entrySet()) {
if (first) {
builder.append("&");
}
builder.append(param.getKey()).append("=").append(param.getValue());
first = true;
}
parameterString = builder.toString();
}
return url+parameterString;
}
@SneakyThrows
public static String parseParametersEncode(String url, Map<String,Object> parameters)
{
var parameterString = "";
if (!parameters.isEmpty()) {
var builder = new StringBuilder();
builder.append("?");
var first = false;
for (var param : parameters.entrySet()) {
if (first) {
builder.append("&");
}
final String encodedKey = URLEncoder.encode(param.getKey(), StandardCharsets.UTF_8);
final String encodedValue = URLEncoder.encode(param.getValue().toString(), StandardCharsets.UTF_8);
builder.append(encodedKey).append("=").append(encodedValue);
first = true;
}
parameterString = builder.toString();
}
return url+parameterString;
}
}

View File

@@ -1,141 +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;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import io.github.jwdeveloper.tiktok.ClientSettings;
import io.github.jwdeveloper.tiktok.data.dto.TikTokUserInfo;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveOfflineHostException;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveRequestException;
import io.github.jwdeveloper.tiktok.live.LiveRoomMeta;
import io.github.jwdeveloper.tiktok.mappers.LiveRoomMetaMapper;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastResponse;
import java.util.HashMap;
import java.util.logging.Logger;
import java.util.regex.Pattern;
public class TikTokApiService {
private final TikTokHttpClient tiktokHttpClient;
private final Logger logger;
private final ClientSettings clientSettings;
public TikTokApiService(TikTokHttpClient apiClient, Logger logger, ClientSettings clientSettings) {
this.tiktokHttpClient = apiClient;
this.logger = logger;
this.clientSettings = clientSettings;
}
public void updateSessionId() {
if (clientSettings.getSessionId() == null) {
return;
}
if (clientSettings.getSessionId().isEmpty()) {
return;
}
tiktokHttpClient.setSessionId(clientSettings.getSessionId());
}
public String fetchRoomId(String userName) {
var userInfo = fetchUserInfoFromTikTokApi(userName);
clientSettings.getClientParameters().put("room_id", userInfo.getRoomId());
logger.info("RoomID -> " + userInfo.getRoomId());
return userInfo.getRoomId();
}
public TikTokUserInfo fetchUserInfoFromTikTokApi(String userName) {
var params = new HashMap<>(clientSettings.getClientParameters());
params.put("uniqueId", userName);
params.put("sourceType", 54);
JsonObject roomData = null;
try {
roomData = tiktokHttpClient.getJsonFromTikTokApi("api-live/user/room/", params);
} catch (Exception e) {
throw new TikTokLiveRequestException("Failed to fetch pre connection room information, it happens when TikTok temporary blocks you. Try to connect again in few minutes");
}
var message = roomData.get("message").getAsString();
if (message.equals("params_error")) {
throw new TikTokLiveRequestException("fetchRoomIdFromTiktokApi -> Unable to fetch roomID, contact with developer");
}
if (message.equals("user_not_found")) {
return new TikTokUserInfo(TikTokUserInfo.UserStatus.NotFound, "");
}
//live -> status 2
//live paused -> 3
//not live -> status 4
var data = roomData.getAsJsonObject("data");
var user = data.getAsJsonObject("user");
var roomId = user.get("roomId").getAsString();
var status = user.get("status").getAsInt();
var statusEnum = switch (status) {
case 2 -> TikTokUserInfo.UserStatus.Live;
case 3 -> TikTokUserInfo.UserStatus.LivePaused;
case 4 -> TikTokUserInfo.UserStatus.Offline;
default -> TikTokUserInfo.UserStatus.NotFound;
};
return new TikTokUserInfo(statusEnum, roomId);
}
public LiveRoomMeta fetchRoomInfo() {
logger.info("Fetching RoomInfo");
try {
var response = tiktokHttpClient.getJsonFromWebcastApi("room/info/", clientSettings.getClientParameters());
if (!response.has("data")) {
var gson = new GsonBuilder().setPrettyPrinting().create();
var json = gson.toJson(response);
throw new TikTokLiveRequestException("room info response does not contains data field: \n"+ json);
}
var mapper = new LiveRoomMetaMapper();
var liveRoomMeta = mapper.map(response);
logger.info("RoomInfo status -> " + liveRoomMeta.getStatus());
return liveRoomMeta;
} catch (Exception e) {
throw new TikTokLiveRequestException("Failed to fetch room info from WebCast server, see stacktrace for more info.", e);
}
}
public WebcastResponse fetchClientData() {
logger.info("Fetching ClientData");
try {
var response = tiktokHttpClient.getSigningServerResponse("im/fetch/", clientSettings.getClientParameters());
clientSettings.getClientParameters().put("cursor", response.getCursor());
clientSettings.getClientParameters().put("internal_ext", response.getInternalExt());
return response;
} catch (Exception e) {
throw new TikTokLiveRequestException("Failed to fetch live websocket connection data", e);
}
}
}

View File

@@ -1,66 +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;
import io.github.jwdeveloper.tiktok.ClientSettings;
import io.github.jwdeveloper.tiktok.Constants;
import io.github.jwdeveloper.tiktok.data.dto.TikTokUserInfo;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveRequestException;
import io.github.jwdeveloper.tiktok.handlers.TikTokEventObserver;
import java.util.concurrent.CompletableFuture;
import java.util.logging.Logger;
import java.util.regex.Pattern;
public class TikTokDataChecker {
public CompletableFuture<Boolean> isOnlineAsync(String hostName) {
return CompletableFuture.supplyAsync(() -> isOnline(hostName));
}
public CompletableFuture<Boolean> isHostNameValidAsync(String hostName) {
return CompletableFuture.supplyAsync(() -> isOnline(hostName));
}
public boolean isOnline(String hostName) {
var data = getApiService().fetchUserInfoFromTikTokApi(hostName);
return data.getUserStatus() == TikTokUserInfo.UserStatus.Live ||
data.getUserStatus() == TikTokUserInfo.UserStatus.LivePaused;
}
public boolean isHostNameValid(String hostName) {
var data = getApiService().fetchUserInfoFromTikTokApi(hostName);
return data.getUserStatus() != TikTokUserInfo.UserStatus.NotFound;
}
public TikTokApiService getApiService() {
var jar = new TikTokCookieJar();
var factory = new TikTokHttpRequestFactory(jar,new TikTokEventObserver());
var client = new TikTokHttpClient(jar, factory);
var settings = new ClientSettings();
settings.setClientParameters(Constants.DefaultClientParams());
var apiService = new TikTokApiService(client, Logger.getGlobal(), settings);
return apiService;
}
}

View File

@@ -1,160 +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;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import io.github.jwdeveloper.tiktok.Constants;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveRequestException;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastResponse;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
public class TikTokHttpClient {
private final TikTokHttpRequestFactory requestFactory;
private final TikTokCookieJar tikTokCookieJar;
public TikTokHttpClient(TikTokCookieJar tikTokCookieJar, TikTokHttpRequestFactory requestFactory) {
this.requestFactory = requestFactory;
this.tikTokCookieJar = tikTokCookieJar;
}
public void setSessionId(String sessionId)
{
tikTokCookieJar.set("sessionid", sessionId);
tikTokCookieJar.set("sessionid_ss", sessionId);
tikTokCookieJar.set("sid_tt", sessionId);
}
public String getLivestreamPage(String userName) {
var url = Constants.TIKTOK_URL_WEB + "@" + userName + "/live/";
var get = getRequest(url, null);
return get;
}
public JsonObject getJsonFromTikTokApi(String path, Map<String,Object> params)
{
var get = getRequest(Constants.TIKTOK_URL_WEB + path, params);
var json = JsonParser.parseString(get);
var jsonObject = json.getAsJsonObject();
return jsonObject;
}
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 getSigningServerResponse(String path, Map<String, Object> parameters) {
var bytes = getSignRequest(Constants.TIKTOK_URL_WEBCAST + path, parameters);
try {
return WebcastResponse.parseFrom(bytes);
}
catch (Exception e)
{
throw new TikTokLiveRequestException("Unable to deserialize message: "+path,e);
}
}
private String postRequest(String url, Map<String, Object> parameters) {
if (parameters == null) {
parameters = new HashMap<>();
}
var request = requestFactory.setQueries(parameters);
return request.post(url);
}
private String getRequest(String url, Map<String, Object> parameters) {
if (parameters == null) {
parameters = new HashMap<>();
}
var request = requestFactory.setQueries(parameters);
return request.get(url);
}
private byte[] getSignRequest(String url, Map<String, Object> parameters) {
url = getSignedUrl(url, parameters);
try {
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder()
.uri(new URI(url))
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofByteArray());
var cookies = response.headers().allValues("Set-Cookie");
for(var cookie : cookies)
{
var split = cookie.split(";")[0].split("=");
var key = split[0];
var value = split[1];
tikTokCookieJar.set(key, value);
}
return response.body();
}
catch (Exception e)
{
throw new TikTokLiveRequestException("Unable to send signature");
}
}
private String getSignedUrl(String url, Map<String, Object> parameters) {
var fullUrl = HttpUtils.parseParameters(url,parameters);
var signParams = new TreeMap<String,Object>();
signParams.put("client", "ttlive-java");
signParams.put("uuc", 1);
signParams.put("url", fullUrl);
var request = requestFactory.setQueries(signParams);
var content = request.get(Constants.TIKTOK_SIGN_API);
try {
var json = JsonParser.parseString(content);
var jsonObject = json.getAsJsonObject();
var signedUrl = jsonObject.get("signedUrl").getAsString();
var userAgent = jsonObject.get("User-Agent").getAsString();
requestFactory.setAgent(userAgent);
return signedUrl;
} catch (Exception e) {
throw new TikTokLiveRequestException("Insufficient values have been supplied for signing. Likely due to an update. Post an issue on GitHub.", e);
}
}
}

View File

@@ -1,171 +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;
import io.github.jwdeveloper.tiktok.Constants;
import io.github.jwdeveloper.tiktok.data.events.http.TikTokHttpResponseEvent;
import io.github.jwdeveloper.tiktok.data.models.http.HttpData;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveRequestException;
import io.github.jwdeveloper.tiktok.handlers.TikTokEventObserver;
import lombok.SneakyThrows;
import java.net.CookieManager;
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
public class TikTokHttpRequestFactory implements TikTokHttpRequest {
private final CookieManager cookieManager;
private final Map<String, String> defaultHeaders;
private final TikTokCookieJar tikTokCookieJar;
private final HttpClient client;
private final TikTokEventObserver eventHandler;
private String query;
public TikTokHttpRequestFactory(TikTokCookieJar tikTokCookieJar, TikTokEventObserver eventHandler) {
this.tikTokCookieJar = tikTokCookieJar;
this.cookieManager = new CookieManager();
this.eventHandler = eventHandler;
defaultHeaders = Constants.DefaultRequestHeaders();
client = HttpClient.newBuilder()
.cookieHandler(cookieManager)
.connectTimeout(Duration.ofSeconds(2))
.build();
}
@SneakyThrows
public String get(String url) {
var uri = URI.create(url);
var requestBuilder = HttpRequest.newBuilder().GET();
for (var header : defaultHeaders.entrySet()) {
if (header.getKey().equals("Connection") || header.getKey().equals("Accept-Encoding")) {
continue;
}
requestBuilder.setHeader(header.getKey(), header.getValue());
}
if (query != null) {
var baseUri = uri.toString();
var requestUri = URI.create(baseUri + "?" + query);
requestBuilder.uri(requestUri);
} else {
requestBuilder.uri(uri);
}
var result = requestBuilder.build();
return getContent(result);
}
@SneakyThrows
public String post(String url) {
var uri = URI.create(url);
var request = HttpRequest.newBuilder().POST(HttpRequest.BodyPublishers.ofString(""));
for (var header : defaultHeaders.entrySet()) {
if (header.getKey().equals("Connection")) {
continue;
}
request.setHeader(header.getKey(), header.getValue());
}
request.setHeader("Content-type", "application/x-www-form-urlencoded; charset=UTF-8");
request.setHeader("Cookie", tikTokCookieJar.parseCookies());
if (query != null) {
var baseUri = uri.toString();
var requestUri = URI.create(baseUri + "?" + query);
request.uri(requestUri);
}
return getContent(request.build());
}
public TikTokHttpRequest setHeader(String key, String value) {
defaultHeaders.put(key, value);
return this;
}
public TikTokHttpRequest setAgent(String value) {
defaultHeaders.put("User-Agent", value);
return this;
}
public TikTokHttpRequest setQueries(Map<String, Object> queries) {
if (queries == null)
return this;
var testMap = new TreeMap<String, Object>(queries);
query = String.join("&", testMap.entrySet().stream().map(x ->
{
var key = x.getKey();
var value = "";
try {
value = URLEncoder.encode(x.getValue().toString(), StandardCharsets.UTF_8);
} catch (Exception e) {
e.printStackTrace();
}
return key + "=" + value;
}).toList());
return this;
}
private String getContent(HttpRequest request) throws Exception {
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
var event = new TikTokHttpResponseEvent(response.uri().toString(), HttpData.map(response), HttpData.map(request));
eventHandler.publish(null, event);
if (response.statusCode() == 404) {
throw new TikTokLiveRequestException("Request responded with 404 NOT_FOUND");
}
if (response.statusCode() != 200) {
throw new TikTokLiveRequestException("Request was unsuccessful " + response.statusCode());
}
var cookies = response.headers().allValues("Set-Cookie");
for (var cookie : cookies) {
var split = cookie.split(";")[0].split("=");
var uri = request.uri();
var key = split[0];
var value = split[1];
tikTokCookieJar.set(key, value);
var map = new HashMap<String, List<String>>();
map.put(key, List.of(value));
cookieManager.put(uri, map);
}
return response.body();
}
}

View File

@@ -0,0 +1,71 @@
/*
* 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.JsonElement;
import com.google.gson.JsonParser;
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)
.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();
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;
}
}

View File

@@ -20,18 +20,19 @@
* 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.mappers;
package io.github.jwdeveloper.tiktok.http.mappers;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import io.github.jwdeveloper.tiktok.data.models.Picture;
import io.github.jwdeveloper.tiktok.data.models.users.User;
import io.github.jwdeveloper.tiktok.data.models.users.UserAttribute;
import io.github.jwdeveloper.tiktok.live.LiveRoomMeta;
import io.github.jwdeveloper.tiktok.data.requests.LiveData;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveRequestException;
import java.util.ArrayList;
public class LiveRoomMetaMapper {
public class LiveDataMapper {
/**
* 0 - Unknown
* 1 - ?
@@ -39,38 +40,44 @@ public class LiveRoomMetaMapper {
* 3 - ?
* 4 - Offline
*/
public LiveRoomMeta map(JsonObject input) {
var liveRoomMeta = new LiveRoomMeta();
public LiveData.Response map(String json) {
var response = new LiveData.Response();
if (!input.has("data")) {
return liveRoomMeta;
response.setJson(json);
var parsedJson = JsonParser.parseString(json);
var jsonObject = parsedJson.getAsJsonObject();
if (!jsonObject.has("data")) {
throw new TikTokLiveRequestException("Data section not found in LiveData.Response");
}
var data = input.getAsJsonObject("data");
var data = jsonObject.getAsJsonObject("data");
if (data.has("status")) {
var status = data.get("status");
var statusId = status.getAsInt();
var statusValue = switch (statusId) {
case 2 -> LiveRoomMeta.LiveRoomStatus.HostOnline;
case 4 -> LiveRoomMeta.LiveRoomStatus.HostOffline;
default -> LiveRoomMeta.LiveRoomStatus.HostNotFound;
case 2 -> LiveData.LiveStatus.HostOnline;
case 4 -> LiveData.LiveStatus.HostOffline;
default -> LiveData.LiveStatus.HostNotFound;
};
liveRoomMeta.setStatus(statusValue);
response.setLiveStatus(statusValue);
} else {
liveRoomMeta.setStatus(LiveRoomMeta.LiveRoomStatus.HostNotFound);
response.setLiveStatus(LiveData.LiveStatus.HostNotFound);
}
if (data.has("age_restricted")) {
var element = data.getAsJsonObject("age_restricted");
var restricted = element.get("restricted").getAsBoolean();
liveRoomMeta.setAgeRestricted(restricted);
response.setAgeRestricted(restricted);
}
if (data.has("title")) {
var element = data.get("title");
var title = element.getAsString();
liveRoomMeta.setTitie(title);
response.setTitle(title);
}
if (data.has("stats")) {
@@ -82,44 +89,41 @@ public class LiveRoomMetaMapper {
var totalUsers = titalUsersElement.getAsInt();
liveRoomMeta.setLikeCount(likes);
liveRoomMeta.setTotalViewers(totalUsers);
response.setLikes(likes);
response.setTotalViewers(totalUsers);
}
if(data.has("user_count"))
{
if (data.has("user_count")) {
var element = data.get("user_count");
var viewers = element.getAsInt();
liveRoomMeta.setViewers(viewers);
response.setViewers(viewers);
}
if(data.has("owner"))
{
if (data.has("owner")) {
var element = data.getAsJsonObject("owner");
var user = getUser(element);
liveRoomMeta.setHost(user);
response.setHost(user);
}
return liveRoomMeta;
return response;
}
public User getUser(JsonObject jsonElement)
{
public User getUser(JsonObject jsonElement) {
var id = jsonElement.get("id").getAsLong();
var name = jsonElement.get("display_id").getAsString();
var profileName = jsonElement.get("nickname").getAsString();
var followElement =jsonElement.getAsJsonObject("follow_info");
var followElement = jsonElement.getAsJsonObject("follow_info");
var followers = followElement.get("follower_count").getAsInt();
var followingCount = followElement.get("following_count").getAsInt();
var pictureElement =jsonElement.getAsJsonObject("avatar_large");
var pictureElement = jsonElement.getAsJsonObject("avatar_large");
var link = pictureElement.getAsJsonArray("url_list").get(1).getAsString();
var picture = new Picture(link);
var user = new User(id,name,profileName,picture,followers,followingCount,new ArrayList<>());
var user = new User(id, name, profileName, picture, followers, followingCount, new ArrayList<>());
user.addAttribute(UserAttribute.LiveHost);
return user;
}

View File

@@ -0,0 +1,66 @@
/*
* 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.LiveUserData;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveRequestException;
public class LiveUserDataMapper
{
public LiveUserData.Response map(String json) {
var jsonObject = JsonParser.parseString(json).getAsJsonObject();
var message = jsonObject.get("message").getAsString();
if (message.equals("params_error")) {
throw new TikTokLiveRequestException("fetchRoomIdFromTiktokApi -> Unable to fetch roomID, contact the developer");
}
if (message.equals("user_not_found")) {
return new LiveUserData.Response(json, LiveUserData.UserStatus.NotFound, "", -1);
}
//live -> status 2
//live paused -> 3
//not live -> status 4
var element = jsonObject.get("data");
if (element.isJsonNull()) {
return new LiveUserData.Response(json, LiveUserData.UserStatus.NotFound, "", -1);
}
var data = element.getAsJsonObject();
var user = data.getAsJsonObject("user");
var roomId = user.get("roomId").getAsString();
var status = user.get("status").getAsInt();
var liveRoom = data.getAsJsonObject("liveRoom");
long startTime = liveRoom.get("startTime").getAsLong();
var statusEnum = switch (status) {
case 2 -> LiveUserData.UserStatus.Live;
case 3 -> LiveUserData.UserStatus.LivePaused;
case 4 -> LiveUserData.UserStatus.Offline;
default -> LiveUserData.UserStatus.NotFound;
};
return new LiveUserData.Response(json, statusEnum, roomId, startTime);
}
}

View File

@@ -0,0 +1,37 @@
/*
* 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);
}
}

View File

@@ -23,6 +23,7 @@
package io.github.jwdeveloper.tiktok.listener;
import io.github.jwdeveloper.tiktok.TikTokLiveEventHandler;
import io.github.jwdeveloper.tiktok.annotations.TikTokEventObserver;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent;
import io.github.jwdeveloper.tiktok.exceptions.TikTokEventListenerMethodException;
@@ -36,10 +37,10 @@ import java.util.HashMap;
import java.util.List;
public class TikTokListenersManager implements ListenersManager {
private final io.github.jwdeveloper.tiktok.handlers.TikTokEventObserver eventObserver;
private final TikTokLiveEventHandler eventObserver;
private final List<ListenerBindingModel> bindingModels;
public TikTokListenersManager(List<TikTokEventListener> listeners, io.github.jwdeveloper.tiktok.handlers.TikTokEventObserver tikTokEventHandler) {
public TikTokListenersManager(List<TikTokEventListener> listeners, TikTokLiveEventHandler tikTokEventHandler) {
this.eventObserver = tikTokEventHandler;
this.bindingModels = new ArrayList<>(listeners.size());
for (var listener : listeners) {
@@ -110,13 +111,14 @@ public class TikTokListenersManager implements ListenersManager {
EventConsumer eventMethodRef = (liveClient, event) ->
{
try {
method.setAccessible(true);
method.invoke(listener, liveClient, event);
} catch (Exception e) {
throw new TikTokEventListenerMethodException(e);
}
};
eventsMap.computeIfAbsent(eventClazz, (a) -> new ArrayList<EventConsumer<?>>()).add(eventMethodRef);
eventsMap.computeIfAbsent(eventClazz, (a) -> new ArrayList<>()).add(eventMethodRef);
}
return new ListenerBindingModel(listener, eventsMap);
}
}
}

View File

@@ -22,99 +22,120 @@
*/
package io.github.jwdeveloper.tiktok.websocket;
import io.github.jwdeveloper.tiktok.ClientSettings;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveException;
import io.github.jwdeveloper.tiktok.handlers.TikTokEventObserver;
import io.github.jwdeveloper.tiktok.handlers.TikTokMessageHandler;
import io.github.jwdeveloper.tiktok.http.HttpUtils;
import io.github.jwdeveloper.tiktok.http.TikTokCookieJar;
import io.github.jwdeveloper.tiktok.*;
import io.github.jwdeveloper.tiktok.data.dto.ProxyData;
import io.github.jwdeveloper.tiktok.data.requests.LiveConnectionData;
import io.github.jwdeveloper.tiktok.data.settings.*;
import io.github.jwdeveloper.tiktok.exceptions.*;
import io.github.jwdeveloper.tiktok.live.LiveClient;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastResponse;
import org.java_websocket.client.WebSocketClient;
import java.net.URI;
import javax.net.ssl.*;
import java.net.Proxy;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.TreeMap;
import java.util.logging.Logger;
public class TikTokWebSocketClient implements SocketClient {
private final ClientSettings clientSettings;
private final TikTokCookieJar tikTokCookieJar;
private final TikTokMessageHandler messageHandler;
private final TikTokEventObserver tikTokEventHandler;
private final LiveClientSettings clientSettings;
private final TikTokLiveMessageHandler messageHandler;
private final TikTokLiveEventHandler tikTokEventHandler;
private WebSocketClient webSocketClient;
private TikTokWebSocketPingingTask pingingTask;
private final TikTokWebSocketPingingTask pingingTask;
private boolean isConnected;
public TikTokWebSocketClient(
TikTokCookieJar tikTokCookieJar,
ClientSettings clientSettings,
TikTokMessageHandler messageHandler,
TikTokEventObserver tikTokEventHandler) {
this.tikTokCookieJar = tikTokCookieJar;
LiveClientSettings clientSettings,
TikTokLiveMessageHandler messageHandler,
TikTokLiveEventHandler tikTokEventHandler) {
this.clientSettings = clientSettings;
this.messageHandler = messageHandler;
this.tikTokEventHandler = tikTokEventHandler;
isConnected = false;
pingingTask = new TikTokWebSocketPingingTask();
}
public void start(WebcastResponse webcastResponse, LiveClient tikTokLiveClient) {
@Override
public void start(LiveConnectionData.Response connectionData, LiveClient liveClient) {
if (isConnected) {
stop();
}
if (webcastResponse.getPushServer().isEmpty() || webcastResponse.getRouteParamsMapMap().isEmpty())
{
throw new TikTokLiveException("Could not find Room");
}
messageHandler.handle(liveClient, connectionData.getWebcastResponse());
var headers = new HashMap<String, String>();
headers.put("Cookie", connectionData.getWebsocketCookies());
webSocketClient = new TikTokWebSocketListener(connectionData.getWebsocketUrl(),
headers,
clientSettings.getHttpSettings().getTimeout().toMillisPart(),
messageHandler,
tikTokEventHandler,
liveClient);
// ProxyClientSettings proxyClientSettings = clientSettings.getHttpSettings().getProxyClientSettings();
// if (proxyClientSettings.isEnabled())
// connectProxy(proxyClientSettings);
// else
connectDefault();
}
private void connectDefault() {
try {
messageHandler.handle(tikTokLiveClient, webcastResponse);
var url = getWebSocketUrl(webcastResponse);
webSocketClient = startWebSocket(url, tikTokLiveClient);
webSocketClient.connect();
pingingTask = new TikTokWebSocketPingingTask();
pingingTask.run(webSocketClient);
isConnected = true;
} catch (Exception e)
{
} catch (Exception e) {
isConnected = false;
throw new TikTokLiveException("Failed to connect to the websocket", e);
}
}
private URI getWebSocketUrl(WebcastResponse webcastResponse) {
var tiktokAccessKey = webcastResponse.getRouteParamsMapMap();
var parameters = new TreeMap<>(clientSettings.getClientParameters());
parameters.putAll(tiktokAccessKey);
var url = webcastResponse.getPushServer();
var parsed = HttpUtils.parseParametersEncode(url, parameters);
return URI.create(parsed);
public void connectProxy(ProxyClientSettings proxySettings) {
try {
if (proxySettings.getType() == Proxy.Type.SOCKS) {
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, new TrustManager[]{new X509TrustManager() {
public void checkClientTrusted(X509Certificate[] x509Certificates, String s) {}
public void checkServerTrusted(X509Certificate[] x509Certificates, String s) {}
public X509Certificate[] getAcceptedIssuers() { return null; }
}}, null);
webSocketClient.setSocketFactory(sc.getSocketFactory());
}
} catch (Exception e) {
// This will never be thrown.
throw new TikTokProxyRequestException("Unable to set Socks proxy SSL instance");
}
while (proxySettings.hasNext()) {
ProxyData proxyData = proxySettings.next();
if (!tryProxyConnection(proxySettings, proxyData)) {
if (proxySettings.isAutoDiscard())
proxySettings.remove();
continue;
}
pingingTask.run(webSocketClient);
isConnected = true;
break;
}
if (!isConnected)
throw new TikTokLiveException("Failed to connect to the websocket");
}
private WebSocketClient startWebSocket(URI url, LiveClient liveClient) {
var cookie = tikTokCookieJar.parseCookies();
var headers = new HashMap<String, String>();
headers.put("Cookie", cookie);
return new TikTokWebSocketListener(url,
headers,
3000,
messageHandler,
tikTokEventHandler,
liveClient);
public boolean tryProxyConnection(ProxyClientSettings proxySettings, ProxyData proxyData) {
try {
webSocketClient.setProxy(new Proxy(proxySettings.getType(), proxyData.toSocketAddress()));
webSocketClient.connect();
return true;
} catch (Exception e) {
return false;
}
}
public void stop()
{
if (isConnected && webSocketClient != null) {
public void stop() {
if (isConnected && webSocketClient != null && webSocketClient.isOpen()) {
webSocketClient.closeConnection(0, "");
pingingTask.stop();
}
webSocketClient = null;
pingingTask = null;
isConnected = false;
}
}
}

View File

@@ -23,103 +23,93 @@
package io.github.jwdeveloper.tiktok.websocket;
import com.google.protobuf.ByteString;
import io.github.jwdeveloper.tiktok.data.events.TikTokConnectedEvent;
import io.github.jwdeveloper.tiktok.data.events.TikTokDisconnectedEvent;
import io.github.jwdeveloper.tiktok.data.events.TikTokErrorEvent;
import io.github.jwdeveloper.tiktok.*;
import io.github.jwdeveloper.tiktok.data.events.*;
import io.github.jwdeveloper.tiktok.exceptions.TikTokProtocolBufferException;
import io.github.jwdeveloper.tiktok.handlers.TikTokEventObserver;
import io.github.jwdeveloper.tiktok.handlers.TikTokMessageHandler;
import io.github.jwdeveloper.tiktok.live.LiveClient;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastPushFrame;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastResponse;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastWebsocketAck;
import io.github.jwdeveloper.tiktok.messages.webcast.*;
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.drafts.Draft_6455;
import org.java_websocket.handshake.ServerHandshake;
import java.net.URI;
import java.nio.ByteBuffer;
import java.util.Map;
import java.util.Optional;
import java.util.*;
public class TikTokWebSocketListener extends WebSocketClient {
private final TikTokMessageHandler messageHandler;
private final TikTokEventObserver tikTokEventHandler;
private final TikTokLiveMessageHandler messageHandler;
private final TikTokLiveEventHandler tikTokEventHandler;
private final LiveClient tikTokLiveClient;
public TikTokWebSocketListener(URI serverUri,
Map<String, String> httpHeaders,
int connectTimeout,
TikTokMessageHandler messageHandler,
TikTokEventObserver tikTokEventHandler,
TikTokLiveMessageHandler messageHandler,
TikTokLiveEventHandler tikTokEventHandler,
LiveClient tikTokLiveClient) {
super(serverUri, new Draft_6455(), httpHeaders,connectTimeout);
super(serverUri, new Draft_6455(), httpHeaders, connectTimeout);
this.messageHandler = messageHandler;
this.tikTokEventHandler = tikTokEventHandler;
this.tikTokLiveClient = tikTokLiveClient;
}
@Override
public void onMessage(ByteBuffer bytes)
{
public void onMessage(ByteBuffer bytes) {
try {
handleBinary(bytes.array());
} catch (Exception e) {
tikTokEventHandler.publish(tikTokLiveClient, new TikTokErrorEvent(e));
}
if(isNotClosing())
{
sendPing();
}
}
@Override
public void onOpen(ServerHandshake serverHandshake) {
tikTokEventHandler.publish(tikTokLiveClient,new TikTokConnectedEvent());
if(isNotClosing())
{
sendPing();
}
}
@Override
public void onClose(int i, String s, boolean b) {
tikTokEventHandler.publish(tikTokLiveClient,new TikTokDisconnectedEvent());
}
@Override
public void onError(Exception error)
{
tikTokEventHandler.publish(tikTokLiveClient,new TikTokErrorEvent(error));
if(isNotClosing())
{
if (isNotClosing()) {
sendPing();
}
}
private void handleBinary(byte[] buffer) {
var websocketMessageOptional = getWebcastWebsocketMessage(buffer);
if (websocketMessageOptional.isEmpty()) {
var websocketPushFrameOptional = getWebcastPushFrame(buffer);
if (websocketPushFrameOptional.isEmpty()) {
return;
}
var websocketMessage = websocketMessageOptional.get();
var webResponse = getWebResponseMessage(websocketMessage.getPayload());
var websocketPushFrame = websocketPushFrameOptional.get();
var webcastResponse = getWebResponseMessage(websocketPushFrame.getPayload());
if(webResponse.getNeedsAck())
{
//For some reason while send ack id, server get disconnected
// sendAckId(webResponse.getFetchInterval());
if (webcastResponse.getNeedsAck()) {
var pushFrameBuilder = WebcastPushFrame.newBuilder();
pushFrameBuilder.setPayloadType("ack");
pushFrameBuilder.setLogId(websocketPushFrame.getLogId());
pushFrameBuilder.setPayload(webcastResponse.getInternalExtBytes());
if (isNotClosing())
{
this.send(pushFrameBuilder.build().toByteArray());
}
}
messageHandler.handle(tikTokLiveClient, webResponse);
messageHandler.handle(tikTokLiveClient, webcastResponse);
}
private Optional<WebcastPushFrame> getWebcastWebsocketMessage(byte[] buffer) {
@Override
public void onOpen(ServerHandshake serverHandshake) {
tikTokEventHandler.publish(tikTokLiveClient, new TikTokConnectedEvent());
if (isNotClosing()) {
sendPing();
}
}
@Override
public void onClose(int code, String reason, boolean remote) {
tikTokEventHandler.publish(tikTokLiveClient, new TikTokDisconnectedEvent(reason));
tikTokLiveClient.disconnect();
}
@Override
public void onError(Exception error) {
tikTokEventHandler.publish(tikTokLiveClient, new TikTokErrorEvent(error));
if (isNotClosing()) {
sendPing();
}
}
private Optional<WebcastPushFrame> getWebcastPushFrame(byte[] buffer) {
try {
var websocketMessage = WebcastPushFrame.parseFrom(buffer);
if (websocketMessage.getPayload().isEmpty()) {
@@ -139,28 +129,12 @@ public class TikTokWebSocketListener extends WebSocketClient {
}
}
private boolean isNotClosing()
{
private boolean isNotClosing() {
return !isClosed() && !isClosing();
}
private void sendAckId(long id) {
var serverInfo = WebcastWebsocketAck
.newBuilder()
.setType("ack")
.setId(id)
.build();
if(isNotClosing())
{
System.out.println("SEND ICK ID "+id);
send(serverInfo.toByteArray());
}
}
@Override
public void onMessage(String s) {
// System.err.println(s);
}
}
}

View File

@@ -1,55 +1,28 @@
/*
* 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.websocket;
import org.java_websocket.WebSocket;
import java.util.Random;
public class TikTokWebSocketPingingTask
{
private Thread thread;
private boolean isRunning = false;
private final int MIN_TIMEOUT = 5;
private final int MAX_TIMEOUT = 100;
private final int MIN_TIMEOUT = 250;
private final int MAX_TIMEOUT = 500;
public void run(WebSocket webSocket)
{
stop();
thread = new Thread(() ->
{
pingTask(webSocket);
});
isRunning =true;
thread = new Thread(() -> pingTask(webSocket));
isRunning = true;
thread.start();
}
public void stop()
{
if(thread != null)
{
if (thread != null)
thread.interrupt();
}
isRunning = false;
}
@@ -57,12 +30,9 @@ public class TikTokWebSocketPingingTask
private void pingTask(WebSocket webSocket)
{
var random = new Random();
while (isRunning)
{
try
{
if(!webSocket.isOpen())
{
while (isRunning) {
try {
if (!webSocket.isOpen()) {
Thread.sleep(100);
continue;
}
@@ -71,11 +41,10 @@ public class TikTokWebSocketPingingTask
var timeout = random.nextInt(MAX_TIMEOUT)+MIN_TIMEOUT;
Thread.sleep(timeout);
}
catch (Exception e)
{
catch (Exception e) {
isRunning = false;
}
}
}
}
}

View File

@@ -1,133 +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;
import com.google.gson.JsonObject;
import io.github.jwdeveloper.tiktok.ClientSettings;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveException;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveRequestException;
import io.github.jwdeveloper.tiktok.live.LiveRoomMeta;
import io.github.jwdeveloper.tiktok.mappers.LiveRoomMetaMapper;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.util.HashMap;
import java.util.logging.Logger;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
public class TikTokApiServiceTest
{
@Mock
TikTokHttpClient tiktokHttpClient;
@Mock
Logger logger;
@Mock
ClientSettings clientSettings;
@InjectMocks
TikTokApiService tikTokApiService;
@Test
void updateSessionId_NullSessionId_DoesNotSetSessionId() {
when(clientSettings.getSessionId()).thenReturn(null);
tikTokApiService.updateSessionId();
verify(tiktokHttpClient, times(0)).setSessionId(anyString());
}
@Test
void updateSessionId_EmptySessionId_DoesNotSetSessionId() {
when(clientSettings.getSessionId()).thenReturn("");
tikTokApiService.updateSessionId();
verify(tiktokHttpClient, times(0)).setSessionId(anyString());
}
@Test
void updateSessionId_ValidSessionId_SetsSessionId() {
when(clientSettings.getSessionId()).thenReturn("validSessionId");
tikTokApiService.updateSessionId();
verify(tiktokHttpClient, times(1)).setSessionId("validSessionId");
}
// @Test
void fetchRoomId_ValidResponse_ReturnsRoomId() throws Exception {
String expectedRoomId = "123456";
String htmlResponse = "room_id=" + expectedRoomId ;
when(tiktokHttpClient.getLivestreamPage(anyString())).thenReturn(htmlResponse);
String roomId = tikTokApiService.fetchRoomId("username");
assertEquals(expectedRoomId, roomId);
verify(clientSettings.getClientParameters()).put("room_id", expectedRoomId);
}
// @Test
void fetchRoomId_ExceptionThrown_ThrowsTikTokLiveRequestException() throws Exception {
when(tiktokHttpClient.getLivestreamPage(anyString())).thenThrow(new Exception("some exception"));
assertThrows(TikTokLiveRequestException.class, () -> {
tikTokApiService.fetchRoomId("username");
});
}
//@Test
void fetchRoomInfo_ValidResponse_ReturnsLiveRoomMeta() throws Exception {
HashMap<String, Object> clientParameters = new HashMap<>();
var mockResponse = new JsonObject(); // Assume JsonObject is from the Gson library
var expectedLiveRoomMeta = new LiveRoomMeta(); // Assume LiveRoomMeta is a simple POJO
when(clientSettings.getClientParameters()).thenReturn(clientParameters);
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();
assertEquals(expectedLiveRoomMeta, liveRoomMeta);
}
// @Test
void fetchRoomInfo_ExceptionThrown_ThrowsTikTokLiveRequestException() throws Exception {
when(tiktokHttpClient.getJsonFromWebcastApi(anyString(), any())).thenThrow(new Exception("some exception"));
assertThrows(TikTokLiveRequestException.class, () -> {
tikTokApiService.fetchRoomInfo();
});
}
}

View File

@@ -1,103 +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;
import io.github.jwdeveloper.tiktok.TikTokLive;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastGiftMessage;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.util.Random;
public class TikTokLiveOnlineCheckerTest {
public boolean enableTests = false;
@Test
public void shouldTestOnline() {
if(!enableTests)
{
return;
}
var TARGET_USER = "bangbetmenygy";
var sut = new TikTokDataChecker();
var result = sut.isOnline(TARGET_USER);
Assertions.assertTrue(result);
}
@Test
public void shouldBeOffline() {
var TARGET_USER = "karacomparetto";
var sut = new TikTokDataChecker();
var result = sut.isOnline(TARGET_USER);
Assertions.assertFalse(result);
}
@Test
public void shouldBeValid() throws InterruptedException {
var TARGET_USER = "dostawcavideo";
var sut = new TikTokDataChecker();
var result = sut.isHostNameValid(TARGET_USER);
TikTokLive.newClient("asdasd")
.onWebsocketResponse((liveClient, event) ->
{
for(var message : event.getResponse().getMessagesList())
{
if(message.getMethod().equals("WebcastGiftMessage"))
{
try
{
var bytes = message.getMethodBytes();
var rawMessage = WebcastGiftMessage.parseFrom(bytes);
var giftName =rawMessage.getGift().getName();
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
});
Assertions.assertTrue(result);
}
@Test
public void shouldNotBeValid() {
var TARGET_USER = "dqagdagda , asdaaasd";
var sut = new TikTokDataChecker();
var result = sut.isHostNameValid(TARGET_USER);
Assertions.assertFalse(result);
}
}

View File

@@ -22,6 +22,7 @@
*/
package io.github.jwdeveloper.tiktok.listener;
import io.github.jwdeveloper.tiktok.TikTokLiveEventHandler;
import io.github.jwdeveloper.tiktok.annotations.TikTokEventObserver;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent;
import io.github.jwdeveloper.tiktok.data.events.gift.TikTokGiftEvent;
@@ -41,12 +42,12 @@ import static org.mockito.Mockito.verify;
class TikTokListenersManagerTest {
private io.github.jwdeveloper.tiktok.handlers.TikTokEventObserver eventObserver;
private TikTokLiveEventHandler eventObserver;
private TikTokListenersManager tikTokListenersManager;
@BeforeEach
void setUp() {
eventObserver = Mockito.mock(io.github.jwdeveloper.tiktok.handlers.TikTokEventObserver.class);
eventObserver = Mockito.mock(TikTokLiveEventHandler.class);
List<TikTokEventListener> listeners = new ArrayList<>();
tikTokListenersManager = new TikTokListenersManager(listeners, eventObserver);
}

View File

@@ -41,7 +41,7 @@
<parent>
<artifactId>TikTokLiveJava</artifactId>
<groupId>io.github.jwdeveloper.tiktok</groupId>
<version>1.0.11-Release</version>
<version>1.1.0-Release</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -60,6 +60,24 @@
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>io.github.jwdeveloper.tiktok</groupId>
<artifactId>extension-collector</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>io.github.jwdeveloper.tiktok</groupId>
<artifactId>extension-recorder</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>io.github.jwdeveloper.tiktok</groupId>
<artifactId>extension-collector</artifactId>
<version>1.1.0-Release</version>
<scope>compile</scope>
</dependency>
</dependencies>
<properties>

View File

@@ -20,44 +20,36 @@
* 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.tools.tester.mockClient.mocks;
package io.github.jwdeveloper.tiktok;
import io.github.jwdeveloper.tiktok.ClientSettings;
import io.github.jwdeveloper.tiktok.http.TikTokApiService;
import io.github.jwdeveloper.tiktok.http.TikTokHttpClient;
import io.github.jwdeveloper.tiktok.live.LiveRoomMeta;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastResponse;
import java.time.Duration;
import java.util.logging.Logger;
public class ApiServiceMock extends TikTokApiService {
public class ChatMessageExample {
public static void main(String[] args) {
public ApiServiceMock(TikTokHttpClient apiClient, Logger logger, ClientSettings clientSettings) {
super(apiClient, logger, clientSettings);
}
var roomData = TikTokLive.requests()
.fetchLiveData("X");
@Override
public void updateSessionId() {
}
@Override
public LiveRoomMeta fetchRoomInfo()
{
var meta = new LiveRoomMeta();
meta.setStatus(LiveRoomMeta.LiveRoomStatus.HostOnline);
return meta;
}
@Override
public WebcastResponse fetchClientData() {
return WebcastResponse.newBuilder().build();
}
var gifts = TikTokLive.requests().fetchGiftsData();
@Override
public String fetchRoomId(String userName) {
return "mock-room-id";
var user = TikTokLive.requests()
.fetchLiveUserData("mark");
TikTokLive.newClient(SimpleExample.TIKTOK_HOSTNAME)
.configure(clientSettings ->
{
clientSettings.setPrintToConsole(true);
clientSettings.getHttpSettings().setTimeout(Duration.ofSeconds(21));
})
.onComment((liveClient, event) ->
{
System.out.println("Chat message: " + event.getUser().getName() + " " + event.getText());
})
.onWebsocketUnhandledMessage((liveClient, event) ->
{
liveClient.getLogger().info(event.getMessage().getMethod());
}).buildAndConnect();
}
}

View File

@@ -0,0 +1,76 @@
/*
* 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;
import io.github.jwdeveloper.tiktok.extension.collector.TikTokLiveCollector;
import java.io.IOException;
import java.util.List;
import java.util.Map;
public class CollectorExample {
private static String mongoUser;
private static String mongoPassword;
private static String mongoDatabase;
public static void main(String[] args) throws IOException {
var collector = TikTokLiveCollector.use(settings ->
{
settings.setConnectionUrl("mongodb+srv://" + mongoUser + ":" + mongoPassword + "@" + mongoDatabase + "/?retryWrites=true&w=majority");
settings.setDatabaseName("tiktok");
});
collector.connectDatabase();
var users = List.of("tehila_723", "dino123597", "domaxyzx", "dash4214", "obserwacje_live");
var sessionTag = "Tag1";
for (var user : users) {
TikTokLive.newClient(user)
.configure(liveClientSettings ->
{
liveClientSettings.setPrintToConsole(true);
})
.onError((liveClient, event) ->
{
event.getException().printStackTrace();
})
.addListener(collector.newListener(Map.of("sessionTag", sessionTag), document ->
{
if (document.get("dataType") == "message") {
return false;
}
return true;
}))
.buildAndConnectAsync();
}
System.in.read();
collector.disconnectDatabase();
}
}

View File

@@ -42,6 +42,11 @@ public class CustomEventExample {
public static void main(String[] args) {
TikTokLive.newClient(SimpleExample.TIKTOK_HOSTNAME)
.configure(clientSettings ->
{
clientSettings.setPrintToConsole(true);
})
.onGift((liveClient, event) ->
{
if (event.getGift().getDiamondCost() > 100)
@@ -49,14 +54,14 @@ public class CustomEventExample {
else
liveClient.publishEvent(new CheapGiftEvent(event.getGift()));
})
.onEvent(CheapGiftEvent.class,(liveClient, event) ->
.onEvent(CheapGiftEvent.class, (liveClient, event) ->
{
System.out.println("Thanks for cheap gift");
})
.onEvent(ExpensiveGiftEvent.class,(liveClient, event) ->
.onEvent(ExpensiveGiftEvent.class, (liveClient, event) ->
{
System.out.println("Thanks for expensive gift!");
})
.build();
.buildAndConnect();
}
}

View File

@@ -57,7 +57,7 @@ public class ListenerExample
/**
*
* Method in TikTokEventListener should meet 4 requirements to be detected
* - must have @TikTokEventHandler annotation
* - must have @TikTokEventObserver annotation
* - must have 2 parameters
* - first parameter must be LiveClient
* - second must be class that extending TikTokEvent

View File

@@ -0,0 +1,52 @@
/*
* 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;
import java.net.Proxy;
public class ProxyExample {
public static void main(String[] args) throws Exception {
TikTokLive.newClient(SimpleExample.TIKTOK_HOSTNAME)
.configure(clientSettings -> {
clientSettings.setPrintToConsole(true);
clientSettings.getHttpSettings().configureProxy(proxySettings -> {
proxySettings.setOnProxyUpdated(proxyData -> System.err.println("Next proxy: " + proxyData.toString()));
proxySettings.setType(Proxy.Type.SOCKS);
proxySettings.addProxy("localhost", 8080);
});
})
.onConnected((liveClient, event) ->
liveClient.getLogger().info("Connected "+liveClient.getRoomInfo().getHostName()))
.onComment((liveClient, event) -> liveClient.getLogger().info(event.getUser().getName()+": "+event.getText()))
.onLike((liveClient, event) -> liveClient.getLogger().info(event.getUser().getName()+" sent "+event.getLikes()+"x likes!"))
.onDisconnected((liveClient, event) ->
liveClient.getLogger().info("Disconnect reason: "+event.getReason()))
.onLiveEnded((liveClient, event) ->
liveClient.getLogger().info("Live Ended: "+liveClient.getRoomInfo().getHostName()))
.onError((liveClient, event) ->
event.getException().printStackTrace())
.buildAndConnect();
System.in.read();
}
}

View File

@@ -0,0 +1,55 @@
/*
* 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;
import io.github.jwdeveloper.tiktok.extension.recorder.TikTokLiveRecorder;
import io.github.jwdeveloper.tiktok.extension.recorder.impl.event.TikTokLiveRecorderStartedEvent;
public class RecorderExample {
public static void main(String[] args) {
TikTokLive.newClient("bangbetmenygy")
.configure(liveClientSettings ->
{
liveClientSettings.setPrintToConsole(true);
})
.onError((liveClient, event) ->
{
event.getException().printStackTrace();
})
.addListener(TikTokLiveRecorder.use(recorderSettings ->
{
recorderSettings.setFfmpegPath("C:\\Users\\ja\\IdeaProjects\\TikTokLiveJava\\extension-recorder\\libs\\ffmpeg.exe");
recorderSettings.setOutputPath("C:\\Users\\ja\\IdeaProjects\\TikTokLiveJava\\extension-recorder\\out");
recorderSettings.setOutputFileName("test.flv");
}))
.onEvent(TikTokLiveRecorderStartedEvent.class, (liveClient, event) ->
{
System.out.println(event.getDownloadData().getFullUrl());
})
.buildAndConnect();
}
}

View File

@@ -23,64 +23,20 @@
package io.github.jwdeveloper.tiktok;
import io.github.jwdeveloper.tiktok.data.events.TikTokSubNotifyEvent;
import io.github.jwdeveloper.tiktok.data.events.TikTokSubscribeEvent;
import io.github.jwdeveloper.tiktok.data.events.envelop.TikTokChestEvent;
import io.github.jwdeveloper.tiktok.data.events.gift.TikTokGiftEvent;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveOfflineHostException;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastGiftMessage;
import io.github.jwdeveloper.tiktok.utils.ConsoleColors;
import io.github.jwdeveloper.tiktok.utils.JsonUtil;
import java.io.IOException;
import java.time.Duration;
import java.util.HashMap;
import java.util.logging.Level;
public class SimpleExample {
public static String TIKTOK_HOSTNAME = "bangbetmenygy";
public static String TIKTOK_HOSTNAME = "dash4114";
public static void main(String[] args) throws IOException {
showLogo();
// set tiktok username
/*
//Optional checking if host name is correct
if(TikTokLive.isHostNameValid(TIKTOK_HOSTNAME))
{
System.out.println("user name exists!");
}
*/
// Optional checking if live is online
if (TikTokLive.isLiveOnline(TIKTOK_HOSTNAME)) {
System.out.println("Live is online!");
}
TikTokLive.newClient("test")
.onWebsocketResponse((liveClient, event) ->
{
var response = event.getResponse();
for (var message : response.getMessagesList()) {
var name = message.getMethod();
var binaryData = message.getPayload();
System.out.println("Event name: " + name);
if (name.equals("WebcastGiftEvent")) {
try {
WebcastGiftMessage giftMessage = WebcastGiftMessage.parseFrom(binaryData);
var giftName = giftMessage.getGift().getName();
var giftId = giftMessage.getGiftId();
var userName = giftMessage.getUser().getNickname();
System.out.println("Gift: "+giftName+" id: "+giftId+" from user: "+userName);
} catch (Exception e) {
throw new RuntimeException("Mapping error", e);
}
}
}
}).buildAndConnect();
TikTokLive.newClient(SimpleExample.TIKTOK_HOSTNAME)
@@ -88,12 +44,14 @@ public class SimpleExample {
{
clientSettings.setHostName(SimpleExample.TIKTOK_HOSTNAME); // This method is useful in case you want change hostname later
clientSettings.setClientLanguage("en"); // Language
clientSettings.setTimeout(Duration.ofSeconds(2)); // Connection timeout
clientSettings.setLogLevel(Level.ALL); // Log level
clientSettings.setPrintToConsole(true); // Printing all logs to console even if log level is Level.OFF
clientSettings.setRetryOnConnectionFailure(true); // Reconnecting if TikTok user is offline
clientSettings.setRetryConnectionTimeout(Duration.ofSeconds(1)); // Timeout before next reconnection
clientSettings.getHttpSettings();
//Optional: Sometimes not every message from chat are send to TikTokLiveJava to fix this issue you can set sessionId
// documentation how to obtain sessionId https://github.com/isaackogan/TikTok-Live-Connector#send-chat-messages
@@ -105,15 +63,6 @@ public class SimpleExample {
//clientSettings.setRoomId("XXXXXXXXXXXXXXXXX");
})
.onConnected((liveClient, event) ->
{
for (var gift : liveClient.getGiftManager().getGifts()) {
gift.getPicture().downloadImageAsync().thenAccept(image ->
{
});
}
})
.onWebsocketMessage((liveClient, event) ->
{
@@ -124,9 +73,14 @@ public class SimpleExample {
}
})
.onWebsocketResponse((liveClient, event) ->
.onEvent((liveClient, event) ->
{
event.getResponse();
if (event instanceof TikTokGiftEvent giftEvent) {
System.out.println("1");
}
if (event instanceof TikTokChestEvent chestEvent) {
System.out.println("2");
}
})
.onGift((liveClient, event) ->
{
@@ -142,7 +96,7 @@ public class SimpleExample {
{
print(ConsoleColors.RED, "GIFT COMBO", event.getGift().getName(), event.getCombo());
})
.onConnected((client, event) ->
.onConnected((liveClient, event) ->
{
print(ConsoleColors.GREEN, "[Connected]");
})
@@ -158,19 +112,19 @@ public class SimpleExample {
{
print(ConsoleColors.BLUE, "Follow:", ConsoleColors.WHITE_BRIGHT, event.getUser().getName());
})
.onJoin((client, event) ->
.onJoin((liveClient, event) ->
{
print(ConsoleColors.WHITE, "Join:", ConsoleColors.WHITE_BRIGHT, event.getUser().getName());
})
.onComment((client, event) ->
.onComment((liveClient, event) ->
{
print(ConsoleColors.GREEN, event.getUser().getName(), ":", ConsoleColors.WHITE_BRIGHT, event.getText());
})
.onEvent((client, event) ->
.onEvent((liveClient, event) ->
{
//System.out.println("Event: " +event.getClass().getSimpleName());
})
.onError((client, event) ->
.onError((liveClient, event) ->
{
event.getException().printStackTrace();
})
@@ -197,4 +151,4 @@ public class SimpleExample {
""");
}
}
}

467
README.md
View File

@@ -29,8 +29,7 @@ A Java library inspired by [TikTokLive](https://github.com/isaackogan/TikTokLive
The library includes a wrapper that connects to the WebCast service using just the username (`uniqueId`). This allows you to connect to your own live chat as well as the live chat of other streamers.
No credentials are required. Events such as [Members Joining](#member), [Gifts](#gift), [Subscriptions](#subscribe), [Viewers](#roomuser), [Follows](#social), [Shares](#social), [Questions](#questionnew), [Likes](#like) and [Battles](#linkmicbattle) can be tracked.
# Contributors
[Library documentation for contributors](https://github.com/jwdeveloper/TikTokLiveJava/wiki)
<div align="center">
<a href="https://www.youtube.com/watch?v=eerWGgUKc6c" align="right" target="blank"><img src="https://img.youtube.com/vi/eerWGgUKc6c/hqdefault.jpg" alt="IMAGE ALT TEXT" width="38%" align="right"></a>
@@ -40,6 +39,7 @@ Join the support [discord](https://discord.gg/e2XwPNTBBr) and visit the `#java-s
Do you prefer other programming languages?
- **Node** orginal: [TikTok-Live-Connector](https://github.com/isaackogan/TikTok-Live-Connector) by [@zerodytrash](https://github.com/zerodytrash)
- **Rust** rewrite: [TikTokLiveRust](https://github.com/jwdeveloper/TikTokLiveRust)
- **Python** rewrite: [TikTokLive](https://github.com/isaackogan/TikTokLive) by [@isaackogan](https://github.com/isaackogan)
- **Go** rewrite: [GoTikTokLive](https://github.com/Davincible/gotiktoklive) by [@Davincible](https://github.com/Davincible)
- **C#** rewrite: [TikTokLiveSharp](https://github.com/frankvHoof93/TikTokLiveSharp) by [@frankvHoof93](https://github.com/frankvHoof93)
@@ -49,6 +49,7 @@ Do you prefer other programming languages?
#### Overview
- [Getting started](#getting-started)
- [Events](#events)
- [Extensions](#extensions)
- [Listeners](#listeners)
- [Contributing](#contributing)
@@ -69,7 +70,7 @@ Maven
<dependency>
<groupId>com.github.jwdeveloper.TikTok-Live-Java</groupId>
<artifactId>Client</artifactId>
<version>1.0.11-Release</version>
<version>1.1.0-Release</version>
<scope>compile</scope>
</dependency>
</dependencies>
@@ -86,7 +87,7 @@ dependencyResolutionManagement {
}
dependencies {
implementation 'com.github.jwdeveloper.TikTok-Live-Java:Client:1.0.11-Release'
implementation 'com.github.jwdeveloper.TikTok-Live-Java:Client:1.1.0-Release'
}
```
@@ -165,37 +166,79 @@ TikTokLive.newClient("bangbetmenygy")
## Events
**Control**:
- [onConnected](#onconnected-tiktokconnectedevent)
- [onDisconnected](#ondisconnected-tiktokdisconnectedevent)
- [onReconnecting](#onreconnecting-tiktokreconnectingevent)
- [onError](#onerror-tiktokerrorevent)
- [onConnected](#onconnected-tiktokconnectedevent)
- [onDisconnected](#ondisconnected-tiktokdisconnectedevent)
**Message**:
- [onEvent](#onevent-tiktokevent)
- [onSubscribe](#onsubscribe-tiktoksubscribeevent)
- [onQuestion](#onquestion-tiktokquestionevent)
- [onFollow](#onfollow-tiktokfollowevent)
- [onLike](#onlike-tiktoklikeevent)
- [onLiveEnded](#onliveended-tiktokliveendedevent)
- [onRoomInfo](#onroominfo-tiktokroominfoevent)
- [onShare](#onshare-tiktokshareevent)
- [onGiftCombo](#ongiftcombo-tiktokgiftcomboevent)
- [onEmote](#onemote-tiktokemoteevent)
- [onGift](#ongift-tiktokgiftevent)
- [onEvent](#onevent-tiktokevent)
- [onComment](#oncomment-tiktokcommentevent)
- [onLivePaused](#onlivepaused-tiktoklivepausedevent)
- [onRoomInfo](#onroominfo-tiktokroominfoevent)
- [onGift](#ongift-tiktokgiftevent)
- [onSubscribe](#onsubscribe-tiktoksubscribeevent)
- [onFollow](#onfollow-tiktokfollowevent)
- [onGiftCombo](#ongiftcombo-tiktokgiftcomboevent)
- [onLiveEnded](#onliveended-tiktokliveendedevent)
- [onQuestion](#onquestion-tiktokquestionevent)
- [onShare](#onshare-tiktokshareevent)
- [onLiveUnpaused](#onliveunpaused-tiktokliveunpausedevent)
- [onEmote](#onemote-tiktokemoteevent)
- [onJoin](#onjoin-tiktokjoinevent)
- [onLike](#onlike-tiktoklikeevent)
- [onLivePaused](#onlivepaused-tiktoklivepausedevent)
**Debug**:
- [onWebsocketUnhandledMessage](#onwebsocketunhandledmessage-tiktokwebsocketunhandledmessageevent)
- [onWebsocketResponse](#onwebsocketresponse-tiktokwebsocketresponseevent)
- [onWebsocketUnhandledMessage](#onwebsocketunhandledmessage-tiktokwebsocketunhandledmessageevent)
- [onHttpResponse](#onhttpresponse-tiktokhttpresponseevent)
- [onWebsocketMessage](#onwebsocketmessage-tiktokwebsocketmessageevent)
# Examples
<br>
## onReconnecting [TikTokReconnectingEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/TikTokReconnectingEvent.java)
```java
TikTokLive.newClient("host-name")
.onReconnecting((liveClient, event) ->
{
})
.buildAndConnect();
```
<br>
## onError [TikTokErrorEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/TikTokErrorEvent.java)
General error event. You should handle this.
```java
TikTokLive.newClient("host-name")
.onError((liveClient, event) ->
{
})
.buildAndConnect();
```
<br>
## onConnected [TikTokConnectedEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/TikTokConnectedEvent.java)
@@ -237,32 +280,15 @@ TikTokLive.newClient("host-name")
<br>
## onReconnecting [TikTokReconnectingEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/TikTokReconnectingEvent.java)
## onEvent [TikTokEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/common/TikTokEvent.java)
```java
TikTokLive.newClient("host-name")
.onReconnecting((liveClient, event) ->
{
})
.buildAndConnect();
```
<br>
## onError [TikTokErrorEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/TikTokErrorEvent.java)
General error event. You should handle this.
Base class for all events
```java
TikTokLive.newClient("host-name")
.onError((liveClient, event) ->
.onEvent((liveClient, event) ->
{
})
@@ -292,91 +318,15 @@ TikTokLive.newClient("host-name")
<br>
## onSubscribe [TikTokSubscribeEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/TikTokSubscribeEvent.java)
## onComment [TikTokCommentEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/TikTokCommentEvent.java)
Triggers when a user creates a subscription.
Triggered every time a new chat comment arrives.
```java
TikTokLive.newClient("host-name")
.onSubscribe((liveClient, event) ->
{
})
.buildAndConnect();
```
<br>
## onQuestion [TikTokQuestionEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/TikTokQuestionEvent.java)
Triggered every time someone asks a new question via the question feature.
```java
TikTokLive.newClient("host-name")
.onQuestion((liveClient, event) ->
{
})
.buildAndConnect();
```
<br>
## onFollow [TikTokFollowEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/social/TikTokFollowEvent.java)
Triggers when a user follows the streamer. Based on social event.
```java
TikTokLive.newClient("host-name")
.onFollow((liveClient, event) ->
{
})
.buildAndConnect();
```
<br>
## onLike [TikTokLikeEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/social/TikTokLikeEvent.java)
Triggered when a viewer sends likes to the streamer. For streams with many viewers, this event is not always triggered by TikTok.
```java
TikTokLive.newClient("host-name")
.onLike((liveClient, event) ->
{
})
.buildAndConnect();
```
<br>
## onLiveEnded [TikTokLiveEndedEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/TikTokLiveEndedEvent.java)
Triggered when the live stream gets terminated by the host. Will also trigger the TikTokDisconnectedEvent event.
```java
TikTokLive.newClient("host-name")
.onLiveEnded((liveClient, event) ->
.onComment((liveClient, event) ->
{
})
@@ -390,6 +340,8 @@ TikTokLive.newClient("host-name")
## onRoomInfo [TikTokRoomInfoEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/room/TikTokRoomInfoEvent.java)
Triggered when LiveRoomInfo got updated such as likes, viewers, ranking ....
```java
TikTokLive.newClient("host-name")
@@ -402,73 +354,6 @@ TikTokLive.newClient("host-name")
<br>
## onShare [TikTokShareEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/social/TikTokShareEvent.java)
Triggers when a user shares the stream. Based on social event.
```java
TikTokLive.newClient("host-name")
.onShare((liveClient, event) ->
{
})
.buildAndConnect();
```
<br>
## onGiftCombo [TikTokGiftComboEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/gift/TikTokGiftComboEvent.java)
Triggered every time gift is sent
@see GiftSendType it has 3 states
<p>Example when user sends gift with combo</p>
<p>>Combo: 1 -> comboState = GiftSendType.Begin</p>
<p>Combo: 4 -> comboState = GiftSendType.Active</p>
<p>Combo: 8 -> comboState = GiftSendType.Active</p>
<p>Combo: 12 -> comboState = GiftSendType.Finished</p>
Remember if comboState is Finished both TikTokGiftComboEvent and TikTokGiftEvent event gets triggered
```java
TikTokLive.newClient("host-name")
.onGiftCombo((liveClient, event) ->
{
})
.buildAndConnect();
```
<br>
## onEmote [TikTokEmoteEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/TikTokEmoteEvent.java)
Triggered every time a subscriber sends an emote (sticker).
```java
TikTokLive.newClient("host-name")
.onEmote((liveClient, event) ->
{
})
.buildAndConnect();
```
<br>
## onGift [TikTokGiftEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/gift/TikTokGiftEvent.java)
@@ -492,15 +377,15 @@ TikTokLive.newClient("host-name")
<br>
## onComment [TikTokCommentEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/TikTokCommentEvent.java)
## onSubscribe [TikTokSubscribeEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/TikTokSubscribeEvent.java)
Triggered every time a new chat comment arrives.
Triggers when a user creates a subscription.
```java
TikTokLive.newClient("host-name")
.onComment((liveClient, event) ->
.onSubscribe((liveClient, event) ->
{
})
@@ -511,13 +396,101 @@ TikTokLive.newClient("host-name")
<br>
## onLivePaused [TikTokLivePausedEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/TikTokLivePausedEvent.java)
## onFollow [TikTokFollowEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/social/TikTokFollowEvent.java)
Triggers when a user follows the streamer. Based on social event.
```java
TikTokLive.newClient("host-name")
.onLivePaused((liveClient, event) ->
.onFollow((liveClient, event) ->
{
})
.buildAndConnect();
```
<br>
## onGiftCombo [TikTokGiftComboEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/gift/TikTokGiftComboEvent.java)
Triggered every time gift is sent
@see GiftSendType it has 3 states
<p>Example when user sends gift with combo</p>
<p>>Combo: 1 -> comboState = GiftSendType.Begin</p>
<p>Combo: 4 -> comboState = GiftSendType.Active</p>
<p>Combo: 8 -> comboState = GiftSendType.Active</p>
<p>Combo: 12 -> comboState = GiftSendType.Finsihed</p>
<p>
Remember if comboState is Finsihed both TikTokGiftComboEvent and TikTokGiftEvent event gets triggered
```java
TikTokLive.newClient("host-name")
.onGiftCombo((liveClient, event) ->
{
})
.buildAndConnect();
```
<br>
## onLiveEnded [TikTokLiveEndedEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/TikTokLiveEndedEvent.java)
Triggered when the live stream gets terminated by the host. Will also trigger the TikTokDisconnectedEvent event.
```java
TikTokLive.newClient("host-name")
.onLiveEnded((liveClient, event) ->
{
})
.buildAndConnect();
```
<br>
## onQuestion [TikTokQuestionEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/TikTokQuestionEvent.java)
Triggered every time someone asks a new question via the question feature.
```java
TikTokLive.newClient("host-name")
.onQuestion((liveClient, event) ->
{
})
.buildAndConnect();
```
<br>
## onShare [TikTokShareEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/social/TikTokShareEvent.java)
Triggers when a user shares the stream. Based on social event.
```java
TikTokLive.newClient("host-name")
.onShare((liveClient, event) ->
{
})
@@ -543,6 +516,25 @@ TikTokLive.newClient("host-name")
<br>
## onEmote [TikTokEmoteEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/TikTokEmoteEvent.java)
Triggered every time a subscriber sends an emote (sticker).
```java
TikTokLive.newClient("host-name")
.onEmote((liveClient, event) ->
{
})
.buildAndConnect();
```
<br>
## onJoin [TikTokJoinEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/social/TikTokJoinEvent.java)
@@ -560,6 +552,59 @@ TikTokLive.newClient("host-name")
<br>
## onLike [TikTokLikeEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/social/TikTokLikeEvent.java)
Triggered when a viewer sends likes to the streamer. For streams with many viewers, this event is not always triggered by TikTok.
```java
TikTokLive.newClient("host-name")
.onLike((liveClient, event) ->
{
})
.buildAndConnect();
```
<br>
## onLivePaused [TikTokLivePausedEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/TikTokLivePausedEvent.java)
```java
TikTokLive.newClient("host-name")
.onLivePaused((liveClient, event) ->
{
})
.buildAndConnect();
```
<br>
## onWebsocketResponse [TikTokWebsocketResponseEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/websocket/TikTokWebsocketResponseEvent.java)
```java
TikTokLive.newClient("host-name")
.onWebsocketResponse((liveClient, event) ->
{
})
.buildAndConnect();
```
<br>
## onWebsocketUnhandledMessage [TikTokWebsocketUnhandledMessageEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/websocket/TikTokWebsocketUnhandledMessageEvent.java)
@@ -581,17 +626,17 @@ TikTokLive.newClient("host-name")
<br>
## onWebsocketResponse [TikTokWebsocketResponseEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/websocket/TikTokWebsocketResponseEvent.java)
## onHttpResponse [TikTokHttpResponseEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/http/TikTokHttpResponseEvent.java)
```java
TikTokLive.newClient("host-name")
.onWebsocketResponse((liveClient, event) ->
{
.onHttpResponse((liveClient, event) ->
{
})
.buildAndConnect();
})
.buildAndConnect();
```
@@ -601,24 +646,37 @@ TikTokLive.newClient("host-name")
## onWebsocketMessage [TikTokWebsocketMessageEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/websocket/TikTokWebsocketMessageEvent.java)
Triggered every time a protobuf encoded webcast message arrives. You can deserialize the binary object depending on the use case.
Triggered every time TikTok sends data. Data incoming as protobuf message.
You can deserialize the binary object depending on the use case.
```java
TikTokLive.newClient("host-name")
.onWebsocketMessage((liveClient, event) ->
{
.onWebsocketMessage((liveClient, event) ->
{
})
.buildAndConnect();
})
.buildAndConnect();
```
<br>
## Extensions
List of extensions (addons) to TiktokLiveJava
that will save your time
- [Video Recorder](https://github.com/jwdeveloper/TikTokLiveJava/tree/master/extension-recorder)
- [Live data collector to database](https://github.com/jwdeveloper/TikTokLiveJava/tree/master/extension-collector)
## Listeners
```java
@@ -651,24 +709,24 @@ public static void main(String[] args) throws IOException {
public static class CustomListener implements TikTokEventListener {
@TikTokEventHandler
@TikTokEventObserver
public void onLike(LiveClient liveClient, TikTokLikeEvent event) {
System.out.println(event.toString());
}
@TikTokEventHandler
@TikTokEventObserver
public void onError(LiveClient liveClient, TikTokErrorEvent event) {
// event.getException().printStackTrace();
}
@TikTokEventHandler
@TikTokEventObserver
public void onComment(LiveClient liveClient, TikTokCommentEvent event) {
var userName = event.getUser().getName();
var text = event.getText();
liveClient.getLogger().info(userName + ": " + text);
}
@TikTokEventHandler
@TikTokEventObserver
public void onGift(LiveClient liveClient, TikTokGiftEvent event) {
var message = switch (event.getGift()) {
case ROSE -> "Thanks :)";
@@ -693,4 +751,7 @@ public static class CustomListener implements TikTokEventListener {
## Contributing
[Library documentation for contributors](https://github.com/jwdeveloper/TikTokLiveJava/wiki)
Your improvements are welcome! Feel free to open an <a href="https://github.com/jwdeveloper/TikTok-Live-Java/issues">issue</a> or <a href="https://github.com/jwdeveloper/TikTok-Live-Java/pulls">pull request</a>.

View File

@@ -5,7 +5,7 @@
<parent>
<artifactId>TikTokLiveJava</artifactId>
<groupId>io.github.jwdeveloper.tiktok</groupId>
<version>1.0.11-Release</version>
<version>1.1.0-Release</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -26,14 +26,10 @@ import io.github.jwdeveloper.tiktok.TikTokLiveClientBuilder;
import io.github.jwdeveloper.tiktok.TikTokRoomInfo;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveException;
import io.github.jwdeveloper.tiktok.gifts.TikTokGiftManager;
import io.github.jwdeveloper.tiktok.handlers.TikTokEventObserver;
import io.github.jwdeveloper.tiktok.handlers.TikTokMessageHandler;
import io.github.jwdeveloper.tiktok.http.TikTokCookieJar;
import io.github.jwdeveloper.tiktok.http.TikTokHttpClient;
import io.github.jwdeveloper.tiktok.http.TikTokHttpRequestFactory;
import io.github.jwdeveloper.tiktok.TikTokLiveMessageHandler;
import io.github.jwdeveloper.tiktok.TikTokLiveHttpClient;
import io.github.jwdeveloper.tiktok.listener.TikTokListenersManager;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastResponse;
import io.github.jwdeveloper.tiktok.tools.tester.mockClient.mocks.ApiServiceMock;
import io.github.jwdeveloper.tiktok.tools.tester.mockClient.mocks.LiveClientMock;
import io.github.jwdeveloper.tiktok.tools.tester.mockClient.mocks.WebsocketClientMock;
@@ -87,21 +83,17 @@ public class TikTokMockBuilder extends TikTokLiveClientBuilder {
public LiveClientMock build() {
validate();
var cookie = new TikTokCookieJar();
var tiktokRoomInfo = new TikTokRoomInfo();
tiktokRoomInfo.setHostName(clientSettings.getHostName());
var listenerManager = new TikTokListenersManager(listeners, tikTokEventHandler);
var giftManager = new TikTokGiftManager(logger);
var requestFactory = new TikTokHttpRequestFactory(cookie, new TikTokEventObserver());
var apiClient = new TikTokHttpClient(cookie, requestFactory);
var apiService = new ApiServiceMock(apiClient, logger, clientSettings);
var mapper = createMapper(giftManager, tiktokRoomInfo);
var handler = new TikTokMessageHandler(tikTokEventHandler, mapper);
var handler = new TikTokLiveMessageHandler(tikTokEventHandler, mapper);
var webSocketClient = new WebsocketClientMock(logger, responses, handler);
return new LiveClientMock(tiktokRoomInfo,
apiService,
new TikTokLiveHttpClient(),
webSocketClient,
giftManager,
tikTokEventHandler,

View File

@@ -22,12 +22,12 @@
*/
package io.github.jwdeveloper.tiktok.tools.tester.mockClient.mocks;
import io.github.jwdeveloper.tiktok.ClientSettings;
import io.github.jwdeveloper.tiktok.data.settings.LiveClientSettings;
import io.github.jwdeveloper.tiktok.TikTokLiveClient;
import io.github.jwdeveloper.tiktok.TikTokRoomInfo;
import io.github.jwdeveloper.tiktok.gifts.TikTokGiftManager;
import io.github.jwdeveloper.tiktok.handlers.TikTokEventObserver;
import io.github.jwdeveloper.tiktok.http.TikTokApiService;
import io.github.jwdeveloper.tiktok.TikTokLiveEventHandler;
import io.github.jwdeveloper.tiktok.TikTokLiveHttpClient;
import io.github.jwdeveloper.tiktok.listener.TikTokListenersManager;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastResponse;
@@ -37,16 +37,18 @@ public class LiveClientMock extends TikTokLiveClient {
private final WebsocketClientMock websocketClientMock;
public LiveClientMock(TikTokRoomInfo tikTokLiveMeta,
TikTokApiService tikTokApiService,
WebsocketClientMock webSocketClient,
TikTokGiftManager tikTokGiftManager,
TikTokEventObserver tikTokEventHandler,
ClientSettings clientSettings,
TikTokListenersManager listenersManager,
Logger logger) {
super(tikTokLiveMeta,
tikTokApiService,
public LiveClientMock(
TikTokRoomInfo tikTokLiveMeta,
TikTokLiveHttpClient httpClient,
WebsocketClientMock webSocketClient,
TikTokGiftManager tikTokGiftManager,
TikTokLiveEventHandler tikTokEventHandler,
LiveClientSettings clientSettings,
TikTokListenersManager listenersManager,
Logger logger) {
super(
tikTokLiveMeta,
httpClient,
webSocketClient,
tikTokGiftManager,
tikTokEventHandler,
@@ -58,7 +60,6 @@ public class LiveClientMock extends TikTokLiveClient {
}
public void publishMessage(String type, String base64) {
websocketClientMock.addMessage(type, base64);
}

View File

@@ -22,10 +22,8 @@
*/
package io.github.jwdeveloper.tiktok.tools.tester.mockClient.mocks;
import com.google.protobuf.ByteString;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveException;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveMessageException;
import io.github.jwdeveloper.tiktok.handlers.TikTokMessageHandler;
import io.github.jwdeveloper.tiktok.data.requests.LiveConnectionData;
import io.github.jwdeveloper.tiktok.TikTokLiveMessageHandler;
import io.github.jwdeveloper.tiktok.live.LiveClient;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastResponse;
import io.github.jwdeveloper.tiktok.websocket.SocketClient;
@@ -40,7 +38,7 @@ public class WebsocketClientMock implements SocketClient {
Logger logger;
Stack<WebcastResponse> responses;
Stack<MsgStruct> messages;
TikTokMessageHandler messageHandler;
TikTokLiveMessageHandler messageHandler;
LiveClient client;
@@ -58,7 +56,7 @@ public class WebsocketClientMock implements SocketClient {
byte[] messageValue;
}
public WebsocketClientMock(Logger logger, Stack<WebcastResponse> responses, TikTokMessageHandler messageHandler) {
public WebsocketClientMock(Logger logger, Stack<WebcastResponse> responses, TikTokLiveMessageHandler messageHandler) {
this.logger = logger;
this.responses = responses;
this.messageHandler = messageHandler;
@@ -91,8 +89,9 @@ public class WebsocketClientMock implements SocketClient {
}
@Override
public void start(WebcastResponse webcastResponse, LiveClient tikTokLiveClient) {
public void start(LiveConnectionData.Response webcastResponse, LiveClient tikTokLiveClient) {
logger.info("Running message: " + responses.size());
@@ -110,7 +109,6 @@ public class WebsocketClientMock implements SocketClient {
});
isRunning = true;
thread.start();
}
@Override

View File

@@ -23,6 +23,7 @@
package io.github.jwdeveloper.tiktok.tools.util;
import com.google.protobuf.ByteString;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastGiftMessage;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastResponse;
import io.github.jwdeveloper.tiktok.utils.ConsoleColors;
import io.github.jwdeveloper.tiktok.utils.JsonUtil;
@@ -34,8 +35,8 @@ public class MessageUtil {
var methodName = message.getMethod();
var inputClazz = Class.forName("io.github.jwdeveloper.tiktok.messages.webcast." + methodName);
var parseMethod = inputClazz.getDeclaredMethod("parseFrom", ByteString.class);
var deserialized = parseMethod.invoke(null, message.getPayload());
return JsonUtil.messageToJson(deserialized);
var webcastObject = parseMethod.invoke(null, message.getPayload());
return JsonUtil.messageToJson(webcastObject);
} catch (Exception ex) {
return ConsoleColors.RED + "Can not find mapper for " + message.getMethod();
@@ -48,6 +49,7 @@ public class MessageUtil {
var inputClazz = Class.forName("io.github.jwdeveloper.tiktok.messages.webcast." + messageName);
var parseMethod = inputClazz.getDeclaredMethod("parseFrom", byte[].class);
var deserialized = parseMethod.invoke(null, bytes);
return JsonUtil.messageToJson(deserialized);
} catch (Exception ex) {
var sb = new StringBuilder();

View File

@@ -5,7 +5,7 @@
<parent>
<artifactId>TikTokLiveJava</artifactId>
<groupId>io.github.jwdeveloper.tiktok</groupId>
<version>1.0.11-Release</version>
<version>1.1.0-Release</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>Tools-EventsWebViewer</artifactId>

View File

@@ -37,7 +37,8 @@ import java.sql.SQLException;
import java.util.concurrent.ExecutionException;
public class Main {
public static void main(String[] args) throws SQLException, ExecutionException, InterruptedException, IOException {
public static void main(String[] args) throws SQLException
{
var settings = new Settings();
settings.setUserName("szalonamoniaxx");
settings.setSessionTag("battle");

View File

@@ -23,6 +23,7 @@
package io.github.jwdeveloper.tiktok.webviewer;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent;
import io.github.jwdeveloper.tiktok.data.models.gifts.Gift;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastGiftMessage;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastLinkLayerMessage;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastLinkMessage;
@@ -32,11 +33,11 @@ import java.io.IOException;
public class ToolsExamples {
private static final String tiktokUser = "debb.cl";
private static final String tiktokUser = "k.peaks";
private static final String db = "db-battle";
private static final String db = "a";
private static final String sessionTag = "gifts";
private static final String sessionTag = "a";
public static void main(String[] args) throws IOException {
// runCollector();
@@ -53,6 +54,7 @@ public class ToolsExamples {
//WebcastLinkMicBattlePunishFinish end of battle?
//WebcastLinkLayerMessage send after end of battle
// send after LinkLayer -> WebcastLinkMessage
private static void runCollector() {
TikTokLiveTools.createCollector(db)
.addUser(tiktokUser)
@@ -60,15 +62,17 @@ public class ToolsExamples {
.configureLiveClient(liveClientBuilder ->
{
liveClientBuilder.configure(clientSettings ->
{
clientSettings.setPrintToConsole(true);
});
liveClientBuilder.onWebsocketResponse((liveClient, event) ->
{
for (var msg : event.getResponse().getMessagesList()) {
System.out.println(msg.getMethod());
}
});
{
clientSettings.setPrintToConsole(true);
})
.onComment((liveClient, event) ->
{
System.out.println("Chat message: " + event.getUser().getName() + " " + event.getText());
})
.onWebsocketUnhandledMessage((liveClient, event) ->
{
liveClient.getLogger().info(event.getMessage().getMethod());
});
liveClientBuilder.onConnected((liveClient, event) ->
{
liveClient.getLogger().info("Connected");

View File

@@ -5,7 +5,7 @@
<parent>
<artifactId>TikTokLiveJava</artifactId>
<groupId>io.github.jwdeveloper.tiktok</groupId>
<version>1.0.11-Release</version>
<version>1.1.0-Release</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -84,7 +84,6 @@ public class CodeExample {
{
settings.setHostName("bangbetmenygy"); // This method is useful in case you want change hostname later
settings.setClientLanguage("en"); // Language
settings.setTimeout(Duration.ofSeconds(2)); // Connection timeout
settings.setLogLevel(Level.ALL); // Log level
settings.setPrintToConsole(true); // Printing all logs to console even if log level is Level.OFF
settings.setRetryOnConnectionFailure(true); // Reconnecting if TikTok user is offline

View File

@@ -126,7 +126,6 @@ TikTokLive.newClient("bangbetmenygy")
{
settings.setHostName("bangbetmenygy"); // This method is useful in case you want change hostname later
settings.setClientLanguage("en"); // Language
settings.setTimeout(Duration.ofSeconds(2)); // Connection timeout
settings.setLogLevel(Level.ALL); // Log level
settings.setPrintToConsole(true); // Printing all logs to console even if log level is Level.OFF
settings.setRetryOnConnectionFailure(true); // Reconnecting if TikTok user is offline
@@ -152,35 +151,73 @@ TikTokLive.newClient("bangbetmenygy")
**Control**:
- [onConnected](#onconnected-tiktokconnectedevent)
- [onDisconnected](#ondisconnected-tiktokdisconnectedevent)
- [onReconnecting](#onreconnecting-tiktokreconnectingevent)
- [onError](#onerror-tiktokerrorevent)
- [onConnected](#onconnected-tiktokconnectedevent)
- [onDisconnected](#ondisconnected-tiktokdisconnectedevent)
**Message**:
- [onEvent](#onevent-tiktokevent)
- [onSubscribe](#onsubscribe-tiktoksubscribeevent)
- [onQuestion](#onquestion-tiktokquestionevent)
- [onFollow](#onfollow-tiktokfollowevent)
- [onLike](#onlike-tiktoklikeevent)
- [onLiveEnded](#onliveended-tiktokliveendedevent)
- [onRoomInfo](#onroominfo-tiktokroominfoevent)
- [onShare](#onshare-tiktokshareevent)
- [onGiftCombo](#ongiftcombo-tiktokgiftcomboevent)
- [onEmote](#onemote-tiktokemoteevent)
- [onGift](#ongift-tiktokgiftevent)
- [onEvent](#onevent-tiktokevent)
- [onComment](#oncomment-tiktokcommentevent)
- [onLivePaused](#onlivepaused-tiktoklivepausedevent)
- [onRoomInfo](#onroominfo-tiktokroominfoevent)
- [onGift](#ongift-tiktokgiftevent)
- [onSubscribe](#onsubscribe-tiktoksubscribeevent)
- [onFollow](#onfollow-tiktokfollowevent)
- [onGiftCombo](#ongiftcombo-tiktokgiftcomboevent)
- [onLiveEnded](#onliveended-tiktokliveendedevent)
- [onQuestion](#onquestion-tiktokquestionevent)
- [onShare](#onshare-tiktokshareevent)
- [onLiveUnpaused](#onliveunpaused-tiktokliveunpausedevent)
- [onEmote](#onemote-tiktokemoteevent)
- [onJoin](#onjoin-tiktokjoinevent)
- [onLike](#onlike-tiktoklikeevent)
- [onLivePaused](#onlivepaused-tiktoklivepausedevent)
**Debug**:
- [onWebsocketUnhandledMessage](#onwebsocketunhandledmessage-tiktokwebsocketunhandledmessageevent)
- [onWebsocketResponse](#onwebsocketresponse-tiktokwebsocketresponseevent)
- [onWebsocketUnhandledMessage](#onwebsocketunhandledmessage-tiktokwebsocketunhandledmessageevent)
- [onHttpResponse](#onhttpresponse-tiktokhttpresponseevent)
- [onWebsocketMessage](#onwebsocketmessage-tiktokwebsocketmessageevent)
# Examples
<br>
## onReconnecting [TikTokReconnectingEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/TikTokReconnectingEvent.java)
```java
TikTokLive.newClient("host-name")
.onReconnecting((liveClient, event) ->
{
})
.buildAndConnect();
```
<br>
## onError [TikTokErrorEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/TikTokErrorEvent.java)
General error event. You should handle this.
```java
TikTokLive.newClient("host-name")
.onError((liveClient, event) ->
{
})
.buildAndConnect();
```
<br>
## onConnected [TikTokConnectedEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/TikTokConnectedEvent.java)
@@ -222,32 +259,15 @@ TikTokLive.newClient("host-name")
<br>
## onReconnecting [TikTokReconnectingEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/TikTokReconnectingEvent.java)
## onEvent [TikTokEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/common/TikTokEvent.java)
```java
TikTokLive.newClient("host-name")
.onReconnecting((liveClient, event) ->
{
})
.buildAndConnect();
```
<br>
## onError [TikTokErrorEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/TikTokErrorEvent.java)
General error event. You should handle this.
Base class for all events
```java
TikTokLive.newClient("host-name")
.onError((liveClient, event) ->
.onEvent((liveClient, event) ->
{
})
@@ -277,15 +297,15 @@ TikTokLive.newClient("host-name")
<br>
## onSubscribe [TikTokSubscribeEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/TikTokSubscribeEvent.java)
## onComment [TikTokCommentEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/TikTokCommentEvent.java)
Triggers when a user creates a subscription.
Triggered every time a new chat comment arrives.
```java
TikTokLive.newClient("host-name")
.onSubscribe((liveClient, event) ->
.onComment((liveClient, event) ->
{
})
@@ -296,15 +316,55 @@ TikTokLive.newClient("host-name")
<br>
## onQuestion [TikTokQuestionEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/TikTokQuestionEvent.java)
## onRoomInfo [TikTokRoomInfoEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/room/TikTokRoomInfoEvent.java)
Triggered every time someone asks a new question via the question feature.
Triggered when LiveRoomInfo got updated such as likes, viewers, ranking ....
```java
TikTokLive.newClient("host-name")
.onQuestion((liveClient, event) ->
.onRoomInfo((liveClient, event) ->
{
})
.buildAndConnect();
```
<br>
## onGift [TikTokGiftEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/gift/TikTokGiftEvent.java)
Triggered when user sends gifts that has
no combo (most of expensive gifts)
or if combo has finished
```java
TikTokLive.newClient("host-name")
.onGift((liveClient, event) ->
{
})
.buildAndConnect();
```
<br>
## onSubscribe [TikTokSubscribeEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/TikTokSubscribeEvent.java)
Triggers when a user creates a subscription.
```java
TikTokLive.newClient("host-name")
.onSubscribe((liveClient, event) ->
{
})
@@ -334,15 +394,25 @@ TikTokLive.newClient("host-name")
<br>
## onLike [TikTokLikeEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/social/TikTokLikeEvent.java)
## onGiftCombo [TikTokGiftComboEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/gift/TikTokGiftComboEvent.java)
Triggered when a viewer sends likes to the streamer. For streams with many viewers, this event is not always triggered by TikTok.
Triggered every time gift is sent
@see GiftSendType it has 3 states
<p>Example when user sends gift with combo</p>
<p>>Combo: 1 -> comboState = GiftSendType.Begin</p>
<p>Combo: 4 -> comboState = GiftSendType.Active</p>
<p>Combo: 8 -> comboState = GiftSendType.Active</p>
<p>Combo: 12 -> comboState = GiftSendType.Finsihed</p>
<p>
Remember if comboState is Finsihed both TikTokGiftComboEvent and TikTokGiftEvent event gets triggered
```java
TikTokLive.newClient("host-name")
.onLike((liveClient, event) ->
.onGiftCombo((liveClient, event) ->
{
})
@@ -372,13 +442,15 @@ TikTokLive.newClient("host-name")
<br>
## onRoomInfo [TikTokRoomInfoEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/room/TikTokRoomInfoEvent.java)
## onQuestion [TikTokQuestionEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/TikTokQuestionEvent.java)
Triggered every time someone asks a new question via the question feature.
```java
TikTokLive.newClient("host-name")
.onRoomInfo((liveClient, event) ->
.onQuestion((liveClient, event) ->
{
})
@@ -408,25 +480,13 @@ TikTokLive.newClient("host-name")
<br>
## onGiftCombo [TikTokGiftComboEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/gift/TikTokGiftComboEvent.java)
## onLiveUnpaused [TikTokLiveUnpausedEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/TikTokLiveUnpausedEvent.java)
Triggered every time gift is sent
@see GiftSendType it has 3 states
<p>Example when user sends gift with combo</p>
<p>>Combo: 1 -> comboState = GiftSendType.Begin</p>
<p>Combo: 4 -> comboState = GiftSendType.Active</p>
<p>Combo: 8 -> comboState = GiftSendType.Active</p>
<p>Combo: 12 -> comboState = GiftSendType.Finsihed</p>
Remember if comboState is Finsihed both TikTokGiftComboEvent and TikTokGiftEvent event gets triggered
```java
TikTokLive.newClient("host-name")
.onGiftCombo((liveClient, event) ->
.onLiveUnpaused((liveClient, event) ->
{
})
@@ -456,17 +516,13 @@ TikTokLive.newClient("host-name")
<br>
## onGift [TikTokGiftEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/gift/TikTokGiftEvent.java)
## onJoin [TikTokJoinEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/social/TikTokJoinEvent.java)
Triggered when user sends gifts that has
no combo (most of expensive gifts)
or if combo has finished
```java
TikTokLive.newClient("host-name")
.onGift((liveClient, event) ->
.onJoin((liveClient, event) ->
{
})
@@ -477,15 +533,15 @@ TikTokLive.newClient("host-name")
<br>
## onComment [TikTokCommentEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/TikTokCommentEvent.java)
## onLike [TikTokLikeEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/social/TikTokLikeEvent.java)
Triggered every time a new chat comment arrives.
Triggered when a viewer sends likes to the streamer. For streams with many viewers, this event is not always triggered by TikTok.
```java
TikTokLive.newClient("host-name")
.onComment((liveClient, event) ->
.onLike((liveClient, event) ->
{
})
@@ -513,30 +569,13 @@ TikTokLive.newClient("host-name")
<br>
## onLiveUnpaused [TikTokLiveUnpausedEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/TikTokLiveUnpausedEvent.java)
## onWebsocketResponse [TikTokWebsocketResponseEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/websocket/TikTokWebsocketResponseEvent.java)
```java
TikTokLive.newClient("host-name")
.onLiveUnpaused((liveClient, event) ->
{
})
.buildAndConnect();
```
<br>
## onJoin [TikTokJoinEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/social/TikTokJoinEvent.java)
```java
TikTokLive.newClient("host-name")
.onJoin((liveClient, event) ->
.onWebsocketResponse((liveClient, event) ->
{
})
@@ -566,13 +605,13 @@ TikTokLive.newClient("host-name")
<br>
## onWebsocketResponse [TikTokWebsocketResponseEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/websocket/TikTokWebsocketResponseEvent.java)
## onHttpResponse [TikTokHttpResponseEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/http/TikTokHttpResponseEvent.java)
```java
TikTokLive.newClient("host-name")
.onWebsocketResponse((liveClient, event) ->
.onHttpResponse((liveClient, event) ->
{
})
@@ -586,7 +625,8 @@ TikTokLive.newClient("host-name")
## onWebsocketMessage [TikTokWebsocketMessageEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/websocket/TikTokWebsocketMessageEvent.java)
Triggered every time a protobuf encoded webcast message arrives. You can deserialize the binary object depending on the use case.
Triggered every time TikTok sends data. Data incoming as protobuf message.
You can deserialize the binary object depending on the use case.
```java
@@ -602,6 +642,17 @@ TikTokLive.newClient("host-name")
{{for item of data }}
{{if item is 2}}
my name is {{item.name}}
{{else}}
{{end}}
{{end}}
<br>
## Listeners
@@ -628,7 +679,7 @@ TikTokLive.newClient("host-name")
/**
*
* Method in TikTokEventListener should meet 4 requirements to be detected
* - must have @TikTokEventHandler annotation
* - must have @TikTokEventObserver annotation
* - must have 2 parameters
* - first parameter must be LiveClient
* - second must be class that extending TikTokEvent
@@ -636,24 +687,24 @@ TikTokLive.newClient("host-name")
public static class CustomListener implements TikTokEventListener {
@TikTokEventHandler
@TikTokEventObserver
public void onLike(LiveClient liveClient, TikTokLikeEvent event) {
System.out.println(event.toString());
}
@TikTokEventHandler
@TikTokEventObserver
public void onError(LiveClient liveClient, TikTokErrorEvent event) {
// event.getException().printStackTrace();
}
@TikTokEventHandler
@TikTokEventObserver
public void onComment(LiveClient liveClient, TikTokCommentEvent event) {
var userName = event.getUser().getName();
var text = event.getText();
liveClient.getLogger().info(userName + ": " + text);
}
@TikTokEventHandler
@TikTokEventObserver
public void onGift(LiveClient liveClient, TikTokGiftEvent event) {
var message = switch (event.getGift()) {
case ROSE -> "Thanks :)";
@@ -666,7 +717,7 @@ TikTokLive.newClient("host-name")
liveClient.getLogger().info(message);
}
@TikTokEventHandler
@TikTokEventObserver
public void onAnyEvent(LiveClient liveClient, TikTokEvent event) {
liveClient.getLogger().info(event.getClass().getSimpleName());
}

View File

@@ -83,6 +83,17 @@ Do you prefer other programming languages?
{{events-content}}
{{for item of data }}
{{if item is 2}}
my name is {{item.name}}
{{else}}
{{end}}
{{end}}
<br>
## Listeners

View File

@@ -5,7 +5,7 @@
<parent>
<artifactId>TikTokLiveJava</artifactId>
<groupId>io.github.jwdeveloper.tiktok</groupId>
<version>1.0.11-Release</version>
<version>1.1.0-Release</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -25,6 +25,7 @@ package io.github.jwdeveloper.tiktok.gifts;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeSpec;
import io.github.jwdeveloper.tiktok.TikTokLive;
import io.github.jwdeveloper.tiktok.data.models.Picture;
import io.github.jwdeveloper.tiktok.gifts.downloader.GiftDto;
import lombok.Getter;
@@ -32,6 +33,7 @@ import lombok.Getter;
import javax.lang.model.element.Modifier;
import java.io.File;
import java.io.IOException;
import java.time.Duration;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@@ -42,10 +44,17 @@ public class GenerateGiftsEnum {
public static void main(String args[]) throws IOException {
TikTokLive.newClient("X")
.configure(liveClientSettings ->
{
var httpSetting = liveClientSettings.getHttpSettings();
httpSetting.setTimeout(Duration.ofSeconds(12));
});
var downloader = new GiftsDownloader();
var gifts = downloader.getGiftsFromFile();
for(var link : gifts)
{
for (var link : gifts) {
System.out.println(link.getImage());
}
var groupedByName = gifts.stream().collect(Collectors.groupingBy(GiftDto::getName));
@@ -105,9 +114,16 @@ public class GenerateGiftsEnum {
.replace("'", "_")
.replace(".", "_")
.replace("-", "_")
.replace("!","_")
.replace("&", "_")
.replace("!", "_")
.toUpperCase();
boolean startsWithNumber = name.matches("^[0-9].*");
if (startsWithNumber) {
name = "_" + name;
}
if (isNumeric(name)) {
name = "_" + name;
}
@@ -141,8 +157,7 @@ public class GenerateGiftsEnum {
}
}
public static TypeSpec addGift(int id, String name, int diamond, String picture)
{
public static TypeSpec addGift(int id, String name, int diamond, String picture) {
return TypeSpec.anonymousClassBuilder(
"$L, $S, $L, $S",
id,

View File

@@ -68,10 +68,10 @@ public class GiftsDownloader {
var officialGifts = officalGift.run();
System.out.println("Official Gifts: " + officialGifts.size());
System.out.println("Downlooading Official Gifts");
System.out.println("Downlooading GiftExtraJson Gifts");
var extraGiftsJson = new GiftExtraJson();
var extraGifts = extraGiftsJson.run();
System.out.println("Official Gifts: " + extraGifts.size());
System.out.println("GiftExtraJson Gifts: " + extraGifts.size());
var outputHashMap = new TreeMap<Integer, GiftDto>();
for (var gift : scraperGifts) {
@@ -86,7 +86,7 @@ public class GiftsDownloader {
var gson = new GsonBuilder().setPrettyPrinting()
.create();
var json = gson.toJson(outputHashMap);
FilesUtility.saveFile("C:\\Users\\ja\\IdeaProjects\\TikTokLiveJava\\Tools\\src\\main\\resources\\gifts\\output.json", json);
FilesUtility.saveFile("C:\\Users\\ja\\IdeaProjects\\TikTokLiveJava\\Tools\\src\\main\\resources\\gifts\\output_1_0_15.json", json);
System.out.println("Gifts saved to file!");
return outputHashMap.values().stream().toList();
}

View File

@@ -23,15 +23,8 @@
package io.github.jwdeveloper.tiktok.gifts.downloader;
import com.google.gson.*;
import io.github.jwdeveloper.tiktok.Constants;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveRequestException;
import io.github.jwdeveloper.tiktok.http.TikTokCookieJar;
import io.github.jwdeveloper.tiktok.http.TikTokHttpClient;
import io.github.jwdeveloper.tiktok.http.TikTokHttpRequestFactory;
import io.github.jwdeveloper.tiktok.utils.FilesUtility;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;

View File

@@ -22,21 +22,14 @@
*/
package io.github.jwdeveloper.tiktok.gifts.downloader;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import io.github.jwdeveloper.tiktok.Constants;
import io.github.jwdeveloper.tiktok.TikTokLive;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveRequestException;
import io.github.jwdeveloper.tiktok.handlers.TikTokEventObserver;
import io.github.jwdeveloper.tiktok.http.TikTokCookieJar;
import io.github.jwdeveloper.tiktok.http.TikTokHttpClient;
import io.github.jwdeveloper.tiktok.http.TikTokHttpRequestFactory;
import io.github.jwdeveloper.tiktok.utils.FilesUtility;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class GiftOfficialJson {
@@ -45,63 +38,30 @@ public class GiftOfficialJson {
}
public List<GiftDto> run() {
var output = new ArrayList<GiftDto>();
var jsonGifts = getJsonGifts();
for (var jsonElement : jsonGifts) {
var gift = getGift(jsonElement);
output.add(gift);
}
return output;
}
private GiftDto getGift(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();
if(image.endsWith(".webp"))
{
image = image.replace(".webp",".jpg");
}
var gift = new GiftDto();
gift.setId(id);
gift.setName(name);
gift.setDiamondCost(diamondCost);
gift.setImage(image);
return gift;
}
public static JsonArray getJsonGifts() {
var jar = new TikTokCookieJar();
var tiktokHttpClient = new TikTokHttpClient(jar, new TikTokHttpRequestFactory(jar, new TikTokEventObserver()));
var settings = Constants.DefaultClientSettings();
var dtf = DateTimeFormatter.ofPattern("dd/MM/yyyy");
var now = LocalDateTime.now();
var date = now.format(dtf).replace("/", "_");
var fileName = "official_" + date + ".json";
try {
var response = tiktokHttpClient.getJsonFromWebcastApi("gift/list/", settings.getClientParameters());
var gson = new GsonBuilder().setPrettyPrinting().create();
FilesUtility.saveFile("C:\\Users\\ja\\IdeaProjects\\TikTokLiveJava\\Tools\\src\\main\\resources\\gifts\\official\\" + fileName, gson.toJson(response));
if (!response.has("data")) {
return new JsonArray();
}
var dataJson = response.getAsJsonObject("data");
if (!dataJson.has("gifts")) {
return new JsonArray();
}
return dataJson.get("gifts").getAsJsonArray();
var dtf = DateTimeFormatter.ofPattern("dd/MM/yyyy");
var now = LocalDateTime.now();
var date = now.format(dtf).replace("/", "_");
var fileName = "official_" + date + ".json";
var httpClient = TikTokLive.requests();
var giftsInfo = httpClient.fetchGiftsData();
FilesUtility.saveFile("C:\\Users\\ja\\IdeaProjects\\TikTokLiveJava\\Tools\\src\\main\\resources\\gifts\\official\\" + fileName, giftsInfo.getJson());
return giftsInfo.getGifts().stream().map(e ->
{
var gift = new GiftDto();
gift.setId(e.getId());
gift.setImage(e.getImage());
gift.setName(e.getName());
gift.setDiamondCost(e.getDiamondCost());
return gift;
}).collect(Collectors.toList());
} catch (Exception e) {
throw new TikTokLiveRequestException("Failed to fetch giftTokens from WebCast, see stacktrace for more info.", e);
}
}
}

View File

@@ -298,5 +298,509 @@
"name": "Team Bracelet",
"diamondCost": 2,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/54cb1eeca369e5bea1b97707ca05d189.png~tplv-obj.png"
},
{
"id": 5514,
"name": "Birds",
"diamondCost": 600,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/0911b5726d912dabbf6ee4b0383352ea.png~tplv-obj.png"
},
{
"id": 5524,
"name": "Tsar",
"diamondCost": 100,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/cb1c3e6263d4b6c08301f8798dcb5a9b.png~tplv-obj.png"
},
{
"id": 5549,
"name": "Ballet Dancer",
"diamondCost": 500,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/c09cc8ce49476d2c46e9c8af6189d5f4.png~tplv-obj.png"
},
{
"id": 5559,
"name": "Crystal Heart",
"diamondCost": 499,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/ae46ac6582a606009643440fe4138eb4.png~tplv-obj.png"
},
{
"id": 5680,
"name": "Disco ball",
"diamondCost": 1000,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/8d0cb854bbe8eeea654f3f9c353c5cf0~tplv-obj.png"
},
{
"id": 6112,
"name": "King Cake ",
"diamondCost": 9,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/aa99da9f6b499ff879c3860e888a53ae~tplv-obj.png"
},
{
"id": 6393,
"name": "Magic Hat",
"diamondCost": 299,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/b156ffd21bb3849a52144ab1688bbc43~tplv-obj.png"
},
{
"id": 6426,
"name": "Dombra",
"diamondCost": 20,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/ccd9fea1988521d1e81051a916800d6c~tplv-obj.png"
},
{
"id": 6452,
"name": "Jakarta Roundabout",
"diamondCost": 16999,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/31f67910fc5858cf087da65746f1f9f3~tplv-obj.png"
},
{
"id": 6633,
"name": "Independence Day",
"diamondCost": 10,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/b967993872a6e40f3477d30545f8d2eb~tplv-obj.png"
},
{
"id": 6655,
"name": "Summer Iris ",
"diamondCost": 30,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/cb591f5b5729fa6e64cac57c78724981~tplv-obj.png"
},
{
"id": 6741,
"name": "Gorgeous Trophy",
"diamondCost": 7000,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/279c9495c2150e333bc4bc13761d177e~tplv-obj.png"
},
{
"id": 6756,
"name": "Hot",
"diamondCost": 10,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/ec679890070187b61620b9662afb814e~tplv-obj.png"
},
{
"id": 6800,
"name": "Pinata",
"diamondCost": 699,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/c8a18d43dc9fb4598d7e991ebeb958ae~tplv-obj.png"
},
{
"id": 6967,
"name": "Autumn Leaves",
"diamondCost": 500,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/30adcaf443df63e3bfd2751ad251f87d~tplv-obj.png"
},
{
"id": 7032,
"name": "Maracas",
"diamondCost": 1,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/00204efcf0573192ad5d872c7beeaf5b~tplv-obj.png"
},
{
"id": 7084,
"name": "Witchy Kitty",
"diamondCost": 30,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/dfce46f99a1206cca84f9092603e4783~tplv-obj.png"
},
{
"id": 7105,
"name": "Magic Potion",
"diamondCost": 499,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/e055625e9239df7e833702c768e033d2~tplv-obj.png"
},
{
"id": 7377,
"name": "Christmas Market G",
"diamondCost": 2000,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/f498f29ef628c8318006a9ff2f49bf08~tplv-obj.png"
},
{
"id": 7458,
"name": "Wooly Hat",
"diamondCost": 199,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/a234d0187047fa48805c8ea2e1f1f756~tplv-obj.png"
},
{
"id": 7475,
"name": "Mistletoe GDM 23",
"diamondCost": 199,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/3527969b8c27e3194e61ff0787a9c3c2~tplv-obj.png"
},
{
"id": 7477,
"name": "Panettone GDM 23",
"diamondCost": 1,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/64ce2413a362442819b4551703b7b26c~tplv-obj.png"
},
{
"id": 7498,
"name": "Candy Cane Gun",
"diamondCost": 799,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/58ef7964e32adc5fc47c5706a02e4ff0~tplv-obj.png"
},
{
"id": 7504,
"name": "Holiday Stocking",
"diamondCost": 5,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/e05de50999ebb446e15c4947b30d3140~tplv-obj.png"
},
{
"id": 7523,
"name": "Hot Choco GDM 23",
"diamondCost": 30,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/f62f5912077d9af84256de288399125a~tplv-obj.png"
},
{
"id": 7525,
"name": "Christmas CarouseG",
"diamondCost": 2000,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/b5ba3941f7389da7495b659e888ea61a~tplv-obj.png"
},
{
"id": 7527,
"name": "Christmas Wreath G",
"diamondCost": 10,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/7842b50135e089334fc40d9705bb53c7~tplv-obj.png"
},
{
"id": 7551,
"name": "Snowman",
"diamondCost": 99,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/e094e0fafc14aaf127fa0d0a7926619a~tplv-obj.png"
},
{
"id": 7697,
"name": "LOVE U",
"diamondCost": 899,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/79d45877691333e2ba69a9098406e95c~tplv-obj.png"
},
{
"id": 7707,
"name": "I'm blue",
"diamondCost": 5,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/c560ec76d5599198aaea9377c5ffab6e~tplv-obj.png"
},
{
"id": 7846,
"name": "Grumpy Glasses",
"diamondCost": 99,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/6f38f8ed7442f69a105788b5c0c74a38~tplv-obj.png"
},
{
"id": 7920,
"name": "Husky",
"diamondCost": 299,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/a2f5d595e9d96aec19a7c0ed5fa9b017~tplv-obj.png"
},
{
"id": 7921,
"name": "Golden",
"diamondCost": 299,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/b97f58dcb0250489ae98529bcb0542ca~tplv-obj.png"
},
{
"id": 8005,
"name": "Falling For You",
"diamondCost": 299,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/a198bd39d2511dbba6a68867740e3ff9~tplv-obj.png"
},
{
"id": 8207,
"name": "The Crown",
"diamondCost": 199,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/5bf798f92fe96ba53c0f4d28f052f9bb~tplv-obj.png"
},
{
"id": 8250,
"name": "Disco ball",
"diamondCost": 1000,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/a53d3ef956eb2f1aa7a7db46024c70bb~tplv-obj.png"
},
{
"id": 8253,
"name": "Spring Train",
"diamondCost": 3999,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/b859c413a241fec75bc78668aeb0f581~tplv-obj.png"
},
{
"id": 8264,
"name": "Happy Weekend",
"diamondCost": 599,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/b04f104e717798235cd3edaa6703e6a3~tplv-obj.png"
},
{
"id": 8265,
"name": "Happy Friday",
"diamondCost": 399,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/65e8fcb76825b9ec36a24faf9a3e9495~tplv-obj.png"
},
{
"id": 8266,
"name": "Good Afternoon",
"diamondCost": 399,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/bff3b908c4dd9cf19ab431cc99dc7940~tplv-obj.png"
},
{
"id": 8269,
"name": "Good Morning",
"diamondCost": 399,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/5c1a28f3aa7eefc27491f3020748ce54~tplv-obj.png"
},
{
"id": 8442,
"name": "Flower Festival",
"diamondCost": 199,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/9bfe63e39b581a69ff944758c3eae5a0~tplv-obj.png"
},
{
"id": 8754,
"name": "Aurora",
"diamondCost": 12000,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/1f59f5593ce135325c1a034825cec18c.png~tplv-obj.png"
},
{
"id": 8803,
"name": "Miss You",
"diamondCost": 1,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/3c53396b922691a7520698f47105a753.png~tplv-obj.png"
},
{
"id": 8804,
"name": "Vacation",
"diamondCost": 1,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/8f46e8eef9cbd5304fb802104c2b4ef4.png~tplv-obj.png"
},
{
"id": 8890,
"name": "Pink Shoes",
"diamondCost": 5,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/cba8a7c718988bd51c7b6055e9ab1ec4.png~tplv-obj.png"
},
{
"id": 9111,
"name": "Popcorn",
"diamondCost": 5,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/676d2d4c31a8979f1fd06cdf5ecd922f~tplv-obj.png"
},
{
"id": 9152,
"name": "Spin with me GDM",
"diamondCost": 199,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/149ac2e87d05490d7d251149cefe27a2.png~tplv-obj.png"
},
{
"id": 9242,
"name": "Pumpkin Spice Latte",
"diamondCost": 10,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/0636d91615f7417ddd5f29438bf5debe~tplv-obj.png"
},
{
"id": 9303,
"name": "Rabbit and Mochi",
"diamondCost": 999,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/213ef2549fbb10ec783c95a41d28cf0a.png~tplv-obj.png"
},
{
"id": 9304,
"name": "Boo the Ghost",
"diamondCost": 88,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/cb909c78f2412e4927ea68d6af8e048f.png~tplv-obj.png"
},
{
"id": 9354,
"name": "I'm here",
"diamondCost": 1,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/7006392a82d57452d5ef08dd90e169c1.png~tplv-obj.png"
},
{
"id": 9355,
"name": "So cute",
"diamondCost": 1,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/d40d31241efcf57c630e894bb3007b8a.png~tplv-obj.png"
},
{
"id": 9363,
"name": "Elf GDM 23",
"diamondCost": 1,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/60e5289b379660cc562742cf987a2d35.png~tplv-obj.png"
},
{
"id": 9535,
"name": "Play for you",
"diamondCost": 299,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/182659e90a3432aa155e61c9c0d89df0.png~tplv-obj.png"
},
{
"id": 9536,
"name": "Fake smile",
"diamondCost": 299,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/35ce62173962e33834703212d0b845a7.png~tplv-obj.png"
},
{
"id": 9576,
"name": "Yeah Nah",
"diamondCost": 1,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/4b20c5aab3841657a343be3769307805.png~tplv-obj.png"
},
{
"id": 9581,
"name": "Turkey Face GDDec",
"diamondCost": 399,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/65349d1ef783fc207c1d2b54a8d521a7.png~tplv-obj.png"
},
{
"id": 9583,
"name": "Cool!",
"diamondCost": 1,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/424c61f16c16919f169fd0352bd24661.png~tplv-obj.png"
},
{
"id": 9587,
"name": "Christmas Potato",
"diamondCost": 10,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/5448f1f5157d3a4a88e0f57acf3dbfe0.png~tplv-obj.png"
},
{
"id": 9604,
"name": "Gobble Gobble",
"diamondCost": 1,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/ada9babc0b55cf005e8c8d13dfc30b42.png~tplv-obj.png"
},
{
"id": 9615,
"name": "Festive Tiny Diny",
"diamondCost": 15,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/f2a8c2967c7153e9077bb469f2e42317.png~tplv-obj.png"
},
{
"id": 9617,
"name": "Xmas Mishka Bear",
"diamondCost": 199,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/700c1c8817847317407cc2b8c6c9da42.png~tplv-obj.png"
},
{
"id": 9625,
"name": "Elf's Hat ",
"diamondCost": 299,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/f9857a040c92b34d6a261201a93c185f.png~tplv-obj.png"
},
{
"id": 9647,
"name": "Kitten Paw",
"diamondCost": 299,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/332520d7b5085ce591396c8d2bb9d352.png~tplv-obj.png"
},
{
"id": 9650,
"name": "The Van Cat",
"diamondCost": 799,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/6973dd1b6d3dee3ca3f0ebac3c1d2977.png~tplv-obj.png"
},
{
"id": 9656,
"name": "Gingerbread man",
"diamondCost": 5,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/af01db3e3cb9f54ea2cb421fab6062bc.png~tplv-obj.png"
},
{
"id": 9657,
"name": "GB North Pole",
"diamondCost": 199,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/79715a53c41619e7b205eb26e57926d4.png~tplv-obj.png"
},
{
"id": 9658,
"name": "DE North Pole",
"diamondCost": 199,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/130e17b5b561a93cefbd236586881477.png~tplv-obj.png"
},
{
"id": 9667,
"name": "Kiwi Bird",
"diamondCost": 10,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/b73cb4aaa76a33efd881192589d65351.png~tplv-obj.png"
},
{
"id": 9668,
"name": "Gingerman Party",
"diamondCost": 1200,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/008a9554e736642f1b2dca9f198bb710.png~tplv-obj.png"
},
{
"id": 9670,
"name": "Reindeer",
"diamondCost": 299,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/4565fa0cd1dbf76463144b0d4cc50bf1.png~tplv-obj.png"
},
{
"id": 9671,
"name": "Gingebread Man",
"diamondCost": 5,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/2399f65414f77419ec7d5e9274dc8e0e.png~tplv-obj.png"
},
{
"id": 9672,
"name": "Mimi & Fifi",
"diamondCost": 5000,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/0a72d0084695d03586fea7d854dc3a47.png~tplv-obj.png"
},
{
"id": 9678,
"name": "Holiday Carousel",
"diamondCost": 2000,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/b5ba3941f7389da7495b659e888ea61a.png~tplv-obj.png"
},
{
"id": 9680,
"name": "Xmas in London",
"diamondCost": 20000,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/876204a6ad0b1b0e4675d9be42439183.png~tplv-obj.png"
},
{
"id": 9682,
"name": "Stay Warm",
"diamondCost": 450,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/abd104eb08ce0c351292036d8897fb8d.png~tplv-obj.png"
},
{
"id": 9688,
"name": "Snowglobe",
"diamondCost": 499,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/ea5ac5f8e186897456bed2e78fc78ca5.png~tplv-obj.png"
},
{
"id": 9698,
"name": "Candy Cane",
"diamondCost": 1,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/1fa0a4ed666304c78a46de200b85c84b.png~tplv-obj.png"
},
{
"id": 9703,
"name": "Really Curious",
"diamondCost": 1,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/793ba68723567b695b12f2ef08dc1484.png~tplv-obj.png"
},
{
"id": 9704,
"name": "Nemo",
"diamondCost": 15,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/68fcf30cb3fb07e9546f5e7fbc2b0ac0.png~tplv-obj.png"
},
{
"id": 9706,
"name": "Elfs Hat ",
"diamondCost": 299,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/f9857a040c92b34d6a261201a93c185f.png~tplv-obj.png"
},
{
"id": 9770,
"name": "Shiba Cookie",
"diamondCost": 10,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/4ea5282e7f61cbeee1214422d40ad407.png~tplv-obj.png"
},
{
"id": 9771,
"name": "KFC Chicken",
"diamondCost": 5,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/f9d59ccd2328b8a46841b3b1c87d9e55.png~tplv-obj.png"
}
]

File diff suppressed because one or more lines are too long

View File

@@ -349,9 +349,9 @@
},
"5514": {
"id": 5514,
"name": "",
"name": "Birds",
"diamondCost": 600,
"image": "https://storage.streamdps.com/iblock/77b/77b29c9978438cbed17bfa7fcfd82a7c/805806ca07c5ed0b4315652ce3952c53.png"
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/0911b5726d912dabbf6ee4b0383352ea.png~tplv-obj.png"
},
"5518": {
"id": 5518,
@@ -365,6 +365,12 @@
"diamondCost": 1,
"image": "https://storage.streamdps.com/iblock/0f1/0f100548d20594a29c36938ceaea1a62/9e8ba4ba863ec74182826cd0ec1204f9.webp"
},
"5524": {
"id": 5524,
"name": "Tsar",
"diamondCost": 100,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/cb1c3e6263d4b6c08301f8798dcb5a9b.png~tplv-obj.png"
},
"5525": {
"id": 5525,
"name": "",
@@ -395,6 +401,12 @@
"diamondCost": 5,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/8525a07c6bf16a74eee66e9ad119b3b8.png~tplv-obj.png"
},
"5549": {
"id": 5549,
"name": "Ballet Dancer",
"diamondCost": 500,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/c09cc8ce49476d2c46e9c8af6189d5f4.png~tplv-obj.png"
},
"5556": {
"id": 5556,
"name": "",
@@ -409,9 +421,9 @@
},
"5559": {
"id": 5559,
"name": "",
"name": "Crystal Heart",
"diamondCost": 499,
"image": "https://storage.streamdps.com/iblock/7ee/7eed736538e3381d1060959a4a6265cc/a0688e0e9dbbf0a18129a01a5787eb05.png"
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/ae46ac6582a606009643440fe4138eb4.png~tplv-obj.png"
},
"5560": {
"id": 5560,
@@ -669,7 +681,7 @@
"id": 5680,
"name": "Disco ball",
"diamondCost": 1000,
"image": "https://storage.streamdps.com/iblock/cdd/cdd759a0d8715e70c1d888c785259620/864a0df77a777eb522157c4d8eb393a3.png"
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/8d0cb854bbe8eeea654f3f9c353c5cf0~tplv-obj.png"
},
"5685": {
"id": 5685,
@@ -1679,6 +1691,12 @@
"diamondCost": 99,
"image": "https://storage.streamdps.com/iblock/755/7554b4a059474294a01241f9065b0b12/3c499a6504236ff8ec6d072747ce0230.png"
},
"6112": {
"id": 6112,
"name": "King Cake ",
"diamondCost": 9,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/aa99da9f6b499ff879c3860e888a53ae~tplv-obj.png"
},
"6113": {
"id": 6113,
"name": "Taco ",
@@ -2071,9 +2089,9 @@
},
"6393": {
"id": 6393,
"name": "",
"name": "Magic Hat",
"diamondCost": 299,
"image": "https://storage.streamdps.com/iblock/9ac/9ac682d8a906e071def06929fca8ac7a/36982973f4f0f8ef9ebb4bb67494f72a.webp"
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/b156ffd21bb3849a52144ab1688bbc43~tplv-obj.png"
},
"6398": {
"id": 6398,
@@ -2123,6 +2141,12 @@
"diamondCost": 2000,
"image": "https://storage.streamdps.com/iblock/49b/49be18ae5914346ffcaf15a519ba9c1c/41326cb23d22010f0c4a8edf5bd27615.webp"
},
"6426": {
"id": 6426,
"name": "Dombra",
"diamondCost": 20,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/ccd9fea1988521d1e81051a916800d6c~tplv-obj.png"
},
"6427": {
"id": 6427,
"name": "Hat and Mustache",
@@ -2189,6 +2213,12 @@
"diamondCost": 1,
"image": "https://storage.streamdps.com/iblock/e11/e110e47562d77ab5fa26cc31e840f801/a4a1823ef2c1bc65c4dc2a4e82ec446b.png"
},
"6452": {
"id": 6452,
"name": "Jakarta Roundabout",
"diamondCost": 16999,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/31f67910fc5858cf087da65746f1f9f3~tplv-obj.png"
},
"6454": {
"id": 6454,
"name": "",
@@ -2561,6 +2591,12 @@
"diamondCost": 99,
"image": "https://storage.streamdps.com/iblock/b17/b171f313a2ba4af15e8645f02d24d4e7/a58831515ce8102cb72841e8ed45ed08.webp"
},
"6633": {
"id": 6633,
"name": "Independence Day",
"diamondCost": 10,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/b967993872a6e40f3477d30545f8d2eb~tplv-obj.png"
},
"6634": {
"id": 6634,
"name": "Sunday Roast",
@@ -2597,6 +2633,12 @@
"diamondCost": 1,
"image": "https://storage.streamdps.com/iblock/c1e/c1e1683273f5505462f45358cd773806/ba5afaec3f62e18672bd1b25a4e8a4d2.webp"
},
"6655": {
"id": 6655,
"name": "Summer Iris ",
"diamondCost": 30,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/cb591f5b5729fa6e64cac57c78724981~tplv-obj.png"
},
"6661": {
"id": 6661,
"name": "Kiss your Heart",
@@ -2705,6 +2747,12 @@
"diamondCost": 100,
"image": "https://storage.streamdps.com/iblock/cc8/cc8133c73d5ca2cb5fde306f5b4e2a11/fb273956755fe6fbf7263023a9c36ebe.webp"
},
"6741": {
"id": 6741,
"name": "Gorgeous Trophy",
"diamondCost": 7000,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/279c9495c2150e333bc4bc13761d177e~tplv-obj.png"
},
"6743": {
"id": 6743,
"name": "",
@@ -2735,6 +2783,12 @@
"diamondCost": 1,
"image": "https://storage.streamdps.com/iblock/457/457a95d19f48aa254b10a30ee8e2c791/b3e217a47d1b38fe06ef11e149aae0e0.webp"
},
"6756": {
"id": 6756,
"name": "Hot",
"diamondCost": 10,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/ec679890070187b61620b9662afb814e~tplv-obj.png"
},
"6757": {
"id": 6757,
"name": "Dash",
@@ -2837,6 +2891,12 @@
"diamondCost": 199,
"image": "https://storage.streamdps.com/iblock/53b/53ba63daed395b1c8eda6cd5fa51b912/2ddcbdeeebee2a97780b4e846aace552.webp"
},
"6800": {
"id": 6800,
"name": "Pinata",
"diamondCost": 699,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/c8a18d43dc9fb4598d7e991ebeb958ae~tplv-obj.png"
},
"6813": {
"id": 6813,
"name": "Fantastic",
@@ -3129,7 +3189,7 @@
"id": 6967,
"name": "Autumn Leaves",
"diamondCost": 500,
"image": "https://storage.streamdps.com/iblock/f04/f042339687e8abaa2fc0e1976d9b11f4/251a0624bc3a23ba39d75467868dcbf8.webp"
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/30adcaf443df63e3bfd2751ad251f87d~tplv-obj.png"
},
"6968": {
"id": 6968,
@@ -3275,6 +3335,12 @@
"diamondCost": 10,
"image": "https://storage.streamdps.com/iblock/eeb/eeb4c2ae379b89120c8ae69bbbea8642/ea6d73fcf39771c592560120d2c9b577.webp"
},
"7032": {
"id": 7032,
"name": "Maracas",
"diamondCost": 1,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/00204efcf0573192ad5d872c7beeaf5b~tplv-obj.png"
},
"7037": {
"id": 7037,
"name": "",
@@ -3385,9 +3451,9 @@
},
"7084": {
"id": 7084,
"name": "",
"name": "Witchy Kitty",
"diamondCost": 30,
"image": "https://storage.streamdps.com/iblock/1c6/1c64bc1b780d8a987986694a699e0585/0d6afd7934d363dba4a747f8c0bec66b.webp"
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/dfce46f99a1206cca84f9092603e4783~tplv-obj.png"
},
"7086": {
"id": 7086,
@@ -3443,6 +3509,12 @@
"diamondCost": 5,
"image": "https://storage.streamdps.com/iblock/5f2/5f2f2f673c116c46372ee874bfb66685/759b52ad2866ed2f5a78ccd558f4457e.webp"
},
"7105": {
"id": 7105,
"name": "Magic Potion",
"diamondCost": 499,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/e055625e9239df7e833702c768e033d2~tplv-obj.png"
},
"7106": {
"id": 7106,
"name": "",
@@ -3737,6 +3809,12 @@
"diamondCost": 1,
"image": "https://storage.streamdps.com/iblock/bf8/bf8e32bd0bf5a916195b5848db571f4f/a9241b01b970aa44c2d5cea5e8ba69d6.webp"
},
"7377": {
"id": 7377,
"name": "Christmas Market G",
"diamondCost": 2000,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/f498f29ef628c8318006a9ff2f49bf08~tplv-obj.png"
},
"7391": {
"id": 7391,
"name": "I\u0027m shy",
@@ -3773,6 +3851,12 @@
"diamondCost": 10,
"image": "https://storage.streamdps.com/iblock/8f7/8f7fcac557fb70fa0bbd69809c112c0e/978e0bcdcd8cbf4cbdfdb53d9fc39eaa.webp"
},
"7458": {
"id": 7458,
"name": "Wooly Hat",
"diamondCost": 199,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/a234d0187047fa48805c8ea2e1f1f756~tplv-obj.png"
},
"7467": {
"id": 7467,
"name": "Chasing the Dream",
@@ -3785,12 +3869,30 @@
"diamondCost": 5000,
"image": "https://storage.streamdps.com/iblock/f5c/f5cda80a1f9853c49226a450faf26e8f/6318d17d7a2526f521123402d19a4c3e.webp"
},
"7475": {
"id": 7475,
"name": "Mistletoe GDM 23",
"diamondCost": 199,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/3527969b8c27e3194e61ff0787a9c3c2~tplv-obj.png"
},
"7477": {
"id": 7477,
"name": "Panettone GDM 23",
"diamondCost": 1,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/64ce2413a362442819b4551703b7b26c~tplv-obj.png"
},
"7482": {
"id": 7482,
"name": "Flying Jets",
"diamondCost": 5000,
"image": "https://storage.streamdps.com/iblock/5a4/5a4f3c7adc31f60326e3adf1a3a20bf9/bc96de02ceba4b91c1f9c996293974b4.webp"
},
"7498": {
"id": 7498,
"name": "Candy Cane Gun",
"diamondCost": 799,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/58ef7964e32adc5fc47c5706a02e4ff0~tplv-obj.png"
},
"7501": {
"id": 7501,
"name": "Hello Traveler",
@@ -3803,6 +3905,30 @@
"diamondCost": 299,
"image": "https://storage.streamdps.com/iblock/679/679e9e38e38f8a4ce707328aa92b8997/9fedb22710af980bbf7b3092b08ecb94.webp"
},
"7504": {
"id": 7504,
"name": "Holiday Stocking",
"diamondCost": 5,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/e05de50999ebb446e15c4947b30d3140~tplv-obj.png"
},
"7523": {
"id": 7523,
"name": "Hot Choco GDM 23",
"diamondCost": 30,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/f62f5912077d9af84256de288399125a~tplv-obj.png"
},
"7525": {
"id": 7525,
"name": "Christmas CarouseG",
"diamondCost": 2000,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/b5ba3941f7389da7495b659e888ea61a~tplv-obj.png"
},
"7527": {
"id": 7527,
"name": "Christmas Wreath G",
"diamondCost": 10,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/7842b50135e089334fc40d9705bb53c7~tplv-obj.png"
},
"7529": {
"id": 7529,
"name": "Mystery Firework",
@@ -3845,6 +3971,12 @@
"diamondCost": 199,
"image": "https://storage.streamdps.com/iblock/e36/e36cb42edac42a0636f8785b80c274dc/bbd87fef7b269564a4ca32009879c0ab.webp"
},
"7551": {
"id": 7551,
"name": "Snowman",
"diamondCost": 99,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/e094e0fafc14aaf127fa0d0a7926619a~tplv-obj.png"
},
"7591": {
"id": 7591,
"name": "Tiny Diny",
@@ -3899,6 +4031,18 @@
"diamondCost": 500,
"image": "https://storage.streamdps.com/iblock/dca/dcac97e4190d46d113f4bdf2918ee173/4fae166b3f3273b9dbbc2a86bea0ec18.webp"
},
"7697": {
"id": 7697,
"name": "LOVE U",
"diamondCost": 899,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/79d45877691333e2ba69a9098406e95c~tplv-obj.png"
},
"7707": {
"id": 7707,
"name": "I\u0027m blue",
"diamondCost": 5,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/c560ec76d5599198aaea9377c5ffab6e~tplv-obj.png"
},
"7720": {
"id": 7720,
"name": "Flying Jets",
@@ -4013,6 +4157,12 @@
"diamondCost": 199,
"image": "https://storage.streamdps.com/iblock/3c4/3c40e306064bb00719cb77dec261891a/bc24ae9f8a52d13475897170332d546f.webp"
},
"7846": {
"id": 7846,
"name": "Grumpy Glasses",
"diamondCost": 99,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/6f38f8ed7442f69a105788b5c0c74a38~tplv-obj.png"
},
"7851": {
"id": 7851,
"name": "Football Helmet",
@@ -4073,6 +4223,18 @@
"diamondCost": 15000,
"image": "https://storage.streamdps.com/iblock/a12/a12a1b23f1f6a19d728de84e1f43e21d/ff288346e9855a9bb6deb4450491028f.webp"
},
"7920": {
"id": 7920,
"name": "Husky",
"diamondCost": 299,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/a2f5d595e9d96aec19a7c0ed5fa9b017~tplv-obj.png"
},
"7921": {
"id": 7921,
"name": "Golden",
"diamondCost": 299,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/b97f58dcb0250489ae98529bcb0542ca~tplv-obj.png"
},
"7927": {
"id": 7927,
"name": "Puppy Love",
@@ -4157,6 +4319,12 @@
"diamondCost": 10,
"image": "https://storage.streamdps.com/iblock/486/486a2490c987c2bb97b6068fd5aac5ab/49d9045fcfe94bbfbd08c3363bb4512a.webp"
},
"8005": {
"id": 8005,
"name": "Falling For You",
"diamondCost": 299,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/a198bd39d2511dbba6a68867740e3ff9~tplv-obj.png"
},
"8017": {
"id": 8017,
"name": "Headphone",
@@ -4289,6 +4457,12 @@
"diamondCost": 5,
"image": "https://storage.streamdps.com/iblock/27f/27fe97cb0f1b8056fa0aedc918ea560e/966df61a39e36fe0f7237e55c3298117.webp"
},
"8207": {
"id": 8207,
"name": "The Crown",
"diamondCost": 199,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/5bf798f92fe96ba53c0f4d28f052f9bb~tplv-obj.png"
},
"8217": {
"id": 8217,
"name": "Capybara",
@@ -4355,6 +4529,18 @@
"diamondCost": 19999,
"image": "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/a598ba4c7024f4d46c1268be4d82f901~tplv-obj.jpg"
},
"8250": {
"id": 8250,
"name": "Disco ball",
"diamondCost": 1000,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/a53d3ef956eb2f1aa7a7db46024c70bb~tplv-obj.png"
},
"8253": {
"id": 8253,
"name": "Spring Train",
"diamondCost": 3999,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/b859c413a241fec75bc78668aeb0f581~tplv-obj.png"
},
"8259": {
"id": 8259,
"name": "I love you",
@@ -4373,6 +4559,24 @@
"diamondCost": 6000,
"image": "https://storage.streamdps.com/iblock/88d/88df4387d65bcc77b691098fd649bd59/ad401a92ddba9aae15bb777f9f38638d.webp"
},
"8264": {
"id": 8264,
"name": "Happy Weekend",
"diamondCost": 599,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/b04f104e717798235cd3edaa6703e6a3~tplv-obj.png"
},
"8265": {
"id": 8265,
"name": "Happy Friday",
"diamondCost": 399,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/65e8fcb76825b9ec36a24faf9a3e9495~tplv-obj.png"
},
"8266": {
"id": 8266,
"name": "Good Afternoon",
"diamondCost": 399,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/bff3b908c4dd9cf19ab431cc99dc7940~tplv-obj.png"
},
"8267": {
"id": 8267,
"name": "Good Evening",
@@ -4385,6 +4589,12 @@
"diamondCost": 399,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/b7b55087141bd5f965eb31a99a5f157b~tplv-obj.png"
},
"8269": {
"id": 8269,
"name": "Good Morning",
"diamondCost": 399,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/5c1a28f3aa7eefc27491f3020748ce54~tplv-obj.png"
},
"8277": {
"id": 8277,
"name": "Love Drop",
@@ -4529,6 +4739,12 @@
"diamondCost": 15000,
"image": "https://storage.streamdps.com/iblock/bfc/bfcf491b940e478b6410047bc047af1b/abbbdd13015a9f31be1b905268873d73.webp"
},
"8442": {
"id": 8442,
"name": "Flower Festival",
"diamondCost": 199,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/9bfe63e39b581a69ff944758c3eae5a0~tplv-obj.png"
},
"8448": {
"id": 8448,
"name": "Raccoon",
@@ -4667,6 +4883,12 @@
"diamondCost": 199,
"image": "https://storage.streamdps.com/iblock/282/28210d38f8625d7c3f39bb85e1fd59c4/61f609f602fc0f3cc2bff6cf94d32fbe.webp"
},
"8754": {
"id": 8754,
"name": "Aurora",
"diamondCost": 12000,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/1f59f5593ce135325c1a034825cec18c.png~tplv-obj.png"
},
"8767": {
"id": 8767,
"name": "Golden Trumpet",
@@ -4691,6 +4913,18 @@
"diamondCost": 450,
"image": "https://storage.streamdps.com/iblock/285/285070af9d4f72b74e7d74c22157f2d9/67d9fa3239a7f9a09ef78c832a66e624.webp"
},
"8803": {
"id": 8803,
"name": "Miss You",
"diamondCost": 1,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/3c53396b922691a7520698f47105a753.png~tplv-obj.png"
},
"8804": {
"id": 8804,
"name": "Vacation",
"diamondCost": 1,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/8f46e8eef9cbd5304fb802104c2b4ef4.png~tplv-obj.png"
},
"8806": {
"id": 8806,
"name": "Dancing Capybaras",
@@ -4757,6 +4991,12 @@
"diamondCost": 1200,
"image": "https://storage.streamdps.com/iblock/07d/07d678346c7eb588bc3cbddf343ab791/8f8f50f5350e4b1c0b151aff333e43a4.webp"
},
"8890": {
"id": 8890,
"name": "Pink Shoes",
"diamondCost": 5,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/cba8a7c718988bd51c7b6055e9ab1ec4.png~tplv-obj.png"
},
"8892": {
"id": 8892,
"name": "Dream Team",
@@ -4859,6 +5099,12 @@
"diamondCost": 1,
"image": "https://storage.streamdps.com/iblock/5b9/5b9eca4a99e965cb25183681a07a5276/c28f7e9c4a8e42460225ff2d12300ae7.webp"
},
"9111": {
"id": 9111,
"name": "Popcorn",
"diamondCost": 5,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/676d2d4c31a8979f1fd06cdf5ecd922f~tplv-obj.png"
},
"9135": {
"id": 9135,
"name": "Magic Forest",
@@ -4895,6 +5141,12 @@
"diamondCost": 3999,
"image": "https://storage.streamdps.com/iblock/990/99093308d3ad2c3833d230f9aae7f702/36f073ad050bd79d7d1ba6d781e7ef11.webp"
},
"9152": {
"id": 9152,
"name": "Spin with me GDM",
"diamondCost": 199,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/149ac2e87d05490d7d251149cefe27a2.png~tplv-obj.png"
},
"9175": {
"id": 9175,
"name": "Pretzel",
@@ -4913,12 +5165,30 @@
"diamondCost": 20000,
"image": "https://storage.streamdps.com/iblock/c79/c793af446369ecef5238e73312c84ccd/464a76f3e6eaee9afc771f45a4bba9df.webp"
},
"9242": {
"id": 9242,
"name": "Pumpkin Spice Latte",
"diamondCost": 10,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/0636d91615f7417ddd5f29438bf5debe~tplv-obj.png"
},
"9255": {
"id": 9255,
"name": "Aerobic headband",
"diamondCost": 99,
"image": "https://storage.streamdps.com/iblock/3d9/3d98c2fbc96922da37a9d22881bb06b9/0a99af132ab8e3fe9806d2412abc6bf0.webp"
},
"9303": {
"id": 9303,
"name": "Rabbit and Mochi",
"diamondCost": 999,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/213ef2549fbb10ec783c95a41d28cf0a.png~tplv-obj.png"
},
"9304": {
"id": 9304,
"name": "Boo the Ghost",
"diamondCost": 88,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/cb909c78f2412e4927ea68d6af8e048f.png~tplv-obj.png"
},
"9333": {
"id": 9333,
"name": "LIVE Fest Clappers",
@@ -4931,6 +5201,30 @@
"diamondCost": 1,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/1e98afffef90ed4b2cc9c9ebb88e3608.png~tplv-obj.png"
},
"9354": {
"id": 9354,
"name": "I\u0027m here",
"diamondCost": 1,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/7006392a82d57452d5ef08dd90e169c1.png~tplv-obj.png"
},
"9355": {
"id": 9355,
"name": "So cute",
"diamondCost": 1,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/d40d31241efcf57c630e894bb3007b8a.png~tplv-obj.png"
},
"9363": {
"id": 9363,
"name": "Elf GDM 23",
"diamondCost": 1,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/60e5289b379660cc562742cf987a2d35.png~tplv-obj.png"
},
"9427": {
"id": 9427,
"name": "Pegasus",
"diamondCost": 42999,
"image": "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/f600a2495ab5d250e7da2066484a9383.png~tplv-obj.jpg"
},
"9463": {
"id": 9463,
"name": "Fairy Wings",
@@ -5003,22 +5297,232 @@
"diamondCost": 12000,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/51a7d74bcb4a6417be59f0ffc0b77e96.png~tplv-obj.png"
},
"9522": {
"id": 9522,
"name": "Exclusive Spark",
"diamondCost": 1000,
"image": "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/f0bda1eb6856e2feea9cfcb6c575c8a0.png~tplv-obj.jpg"
"9535": {
"id": 9535,
"name": "Play for you",
"diamondCost": 299,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/182659e90a3432aa155e61c9c0d89df0.png~tplv-obj.png"
},
"9523": {
"id": 9523,
"name": "Exclusive Jet",
"9536": {
"id": 9536,
"name": "Fake smile",
"diamondCost": 299,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/35ce62173962e33834703212d0b845a7.png~tplv-obj.png"
},
"9576": {
"id": 9576,
"name": "Yeah Nah",
"diamondCost": 1,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/4b20c5aab3841657a343be3769307805.png~tplv-obj.png"
},
"9581": {
"id": 9581,
"name": "Turkey Face GDDec",
"diamondCost": 399,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/65349d1ef783fc207c1d2b54a8d521a7.png~tplv-obj.png"
},
"9583": {
"id": 9583,
"name": "Cool!",
"diamondCost": 1,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/424c61f16c16919f169fd0352bd24661.png~tplv-obj.png"
},
"9587": {
"id": 9587,
"name": "Christmas Potato",
"diamondCost": 10,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/5448f1f5157d3a4a88e0f57acf3dbfe0.png~tplv-obj.png"
},
"9604": {
"id": 9604,
"name": "Gobble Gobble",
"diamondCost": 1,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/ada9babc0b55cf005e8c8d13dfc30b42.png~tplv-obj.png"
},
"9615": {
"id": 9615,
"name": "Festive Tiny Diny",
"diamondCost": 15,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/f2a8c2967c7153e9077bb469f2e42317.png~tplv-obj.png"
},
"9617": {
"id": 9617,
"name": "Xmas Mishka Bear",
"diamondCost": 199,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/700c1c8817847317407cc2b8c6c9da42.png~tplv-obj.png"
},
"9625": {
"id": 9625,
"name": "Elf\u0027s Hat ",
"diamondCost": 299,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/f9857a040c92b34d6a261201a93c185f.png~tplv-obj.png"
},
"9639": {
"id": 9639,
"name": "2024",
"diamondCost": 1,
"image": "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/484a44bbe62ce47687d1da31a6602bbd.png~tplv-obj.jpg"
},
"9640": {
"id": 9640,
"name": "2024 Glasses",
"diamondCost": 224,
"image": "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/512d2e9934cbae8d1019a391814edbd2.png~tplv-obj.jpg"
},
"9641": {
"id": 9641,
"name": "2024 Countdown",
"diamondCost": 2024,
"image": "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/a358a3ce18241dcc6e7d0b02d091d563.png~tplv-obj.jpg"
},
"9642": {
"id": 9642,
"name": "Train to 2024",
"diamondCost": 12024,
"image": "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/0cb12913e0a96bbcc45f97b450e74cd3.png~tplv-obj.jpg"
},
"9643": {
"id": 9643,
"name": "2024 JoyLens",
"diamondCost": 224,
"image": "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/36d82bbcd87c1914df84262d9bdd9b95.png~tplv-obj.jpg"
},
"9644": {
"id": 9644,
"name": "Sparkling Countdown",
"diamondCost": 2024,
"image": "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/d6b4539ed9683707bdefe268f6575e74.png~tplv-obj.jpg"
},
"9645": {
"id": 9645,
"name": "New Year Journey",
"diamondCost": 12024,
"image": "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/0ef9654d92354172fb9b3b364827940c.png~tplv-obj.jpg"
},
"9647": {
"id": 9647,
"name": "Kitten Paw",
"diamondCost": 299,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/332520d7b5085ce591396c8d2bb9d352.png~tplv-obj.png"
},
"9650": {
"id": 9650,
"name": "The Van Cat",
"diamondCost": 799,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/6973dd1b6d3dee3ca3f0ebac3c1d2977.png~tplv-obj.png"
},
"9656": {
"id": 9656,
"name": "Gingerbread man",
"diamondCost": 5,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/af01db3e3cb9f54ea2cb421fab6062bc.png~tplv-obj.png"
},
"9657": {
"id": 9657,
"name": "GB North Pole",
"diamondCost": 199,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/79715a53c41619e7b205eb26e57926d4.png~tplv-obj.png"
},
"9658": {
"id": 9658,
"name": "DE North Pole",
"diamondCost": 199,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/130e17b5b561a93cefbd236586881477.png~tplv-obj.png"
},
"9667": {
"id": 9667,
"name": "Kiwi Bird",
"diamondCost": 10,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/b73cb4aaa76a33efd881192589d65351.png~tplv-obj.png"
},
"9668": {
"id": 9668,
"name": "Gingerman Party",
"diamondCost": 1200,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/008a9554e736642f1b2dca9f198bb710.png~tplv-obj.png"
},
"9670": {
"id": 9670,
"name": "Reindeer",
"diamondCost": 299,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/4565fa0cd1dbf76463144b0d4cc50bf1.png~tplv-obj.png"
},
"9671": {
"id": 9671,
"name": "Gingebread Man",
"diamondCost": 5,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/2399f65414f77419ec7d5e9274dc8e0e.png~tplv-obj.png"
},
"9672": {
"id": 9672,
"name": "Mimi \u0026 Fifi",
"diamondCost": 5000,
"image": "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/1cc167a00aa4d5dfe48018afb38c3daa.png~tplv-obj.jpg"
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/0a72d0084695d03586fea7d854dc3a47.png~tplv-obj.png"
},
"9524": {
"id": 9524,
"name": "Exclusive Yacht",
"9678": {
"id": 9678,
"name": "Holiday Carousel",
"diamondCost": 2000,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/b5ba3941f7389da7495b659e888ea61a.png~tplv-obj.png"
},
"9680": {
"id": 9680,
"name": "Xmas in London",
"diamondCost": 20000,
"image": "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/da8c85d5ae09ebf320216202e8fa015d.png~tplv-obj.jpg"
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/876204a6ad0b1b0e4675d9be42439183.png~tplv-obj.png"
},
"9682": {
"id": 9682,
"name": "Stay Warm",
"diamondCost": 450,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/abd104eb08ce0c351292036d8897fb8d.png~tplv-obj.png"
},
"9688": {
"id": 9688,
"name": "Snowglobe",
"diamondCost": 499,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/ea5ac5f8e186897456bed2e78fc78ca5.png~tplv-obj.png"
},
"9698": {
"id": 9698,
"name": "Candy Cane",
"diamondCost": 1,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/1fa0a4ed666304c78a46de200b85c84b.png~tplv-obj.png"
},
"9703": {
"id": 9703,
"name": "Really Curious",
"diamondCost": 1,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/793ba68723567b695b12f2ef08dc1484.png~tplv-obj.png"
},
"9704": {
"id": 9704,
"name": "Nemo",
"diamondCost": 15,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/68fcf30cb3fb07e9546f5e7fbc2b0ac0.png~tplv-obj.png"
},
"9706": {
"id": 9706,
"name": "Elfs Hat ",
"diamondCost": 299,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/f9857a040c92b34d6a261201a93c185f.png~tplv-obj.png"
},
"9717": {
"id": 9717,
"name": "Lucky Airdrop Box",
"diamondCost": 999,
"image": "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/6ae56f08ae3ee57ea2dda0025bfd39d3.png~tplv-obj.jpg"
},
"9770": {
"id": 9770,
"name": "Shiba Cookie",
"diamondCost": 10,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/4ea5282e7f61cbeee1214422d40ad407.png~tplv-obj.png"
},
"9771": {
"id": 9771,
"name": "KFC Chicken",
"diamondCost": 5,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/f9d59ccd2328b8a46841b3b1c87d9e55.png~tplv-obj.png"
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -349,9 +349,9 @@
},
"5514": {
"id": 5514,
"name": "",
"name": "Birds",
"diamondCost": 600,
"image": "https://storage.streamdps.com/iblock/77b/77b29c9978438cbed17bfa7fcfd82a7c/805806ca07c5ed0b4315652ce3952c53.png"
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/0911b5726d912dabbf6ee4b0383352ea.png~tplv-obj.png"
},
"5518": {
"id": 5518,
@@ -365,6 +365,12 @@
"diamondCost": 1,
"image": "https://storage.streamdps.com/iblock/0f1/0f100548d20594a29c36938ceaea1a62/9e8ba4ba863ec74182826cd0ec1204f9.webp"
},
"5524": {
"id": 5524,
"name": "Tsar",
"diamondCost": 100,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/cb1c3e6263d4b6c08301f8798dcb5a9b.png~tplv-obj.png"
},
"5525": {
"id": 5525,
"name": "",
@@ -395,6 +401,12 @@
"diamondCost": 5,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/8525a07c6bf16a74eee66e9ad119b3b8.png~tplv-obj.png"
},
"5549": {
"id": 5549,
"name": "Ballet Dancer",
"diamondCost": 500,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/c09cc8ce49476d2c46e9c8af6189d5f4.png~tplv-obj.png"
},
"5556": {
"id": 5556,
"name": "",
@@ -409,9 +421,9 @@
},
"5559": {
"id": 5559,
"name": "",
"name": "Crystal Heart",
"diamondCost": 499,
"image": "https://storage.streamdps.com/iblock/7ee/7eed736538e3381d1060959a4a6265cc/a0688e0e9dbbf0a18129a01a5787eb05.png"
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/ae46ac6582a606009643440fe4138eb4.png~tplv-obj.png"
},
"5560": {
"id": 5560,
@@ -669,7 +681,7 @@
"id": 5680,
"name": "Disco ball",
"diamondCost": 1000,
"image": "https://storage.streamdps.com/iblock/cdd/cdd759a0d8715e70c1d888c785259620/864a0df77a777eb522157c4d8eb393a3.png"
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/8d0cb854bbe8eeea654f3f9c353c5cf0~tplv-obj.png"
},
"5685": {
"id": 5685,
@@ -1679,6 +1691,12 @@
"diamondCost": 99,
"image": "https://storage.streamdps.com/iblock/755/7554b4a059474294a01241f9065b0b12/3c499a6504236ff8ec6d072747ce0230.png"
},
"6112": {
"id": 6112,
"name": "King Cake ",
"diamondCost": 9,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/aa99da9f6b499ff879c3860e888a53ae~tplv-obj.png"
},
"6113": {
"id": 6113,
"name": "Taco ",
@@ -2071,9 +2089,9 @@
},
"6393": {
"id": 6393,
"name": "",
"name": "Magic Hat",
"diamondCost": 299,
"image": "https://storage.streamdps.com/iblock/9ac/9ac682d8a906e071def06929fca8ac7a/36982973f4f0f8ef9ebb4bb67494f72a.webp"
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/b156ffd21bb3849a52144ab1688bbc43~tplv-obj.png"
},
"6398": {
"id": 6398,
@@ -2123,6 +2141,12 @@
"diamondCost": 2000,
"image": "https://storage.streamdps.com/iblock/49b/49be18ae5914346ffcaf15a519ba9c1c/41326cb23d22010f0c4a8edf5bd27615.webp"
},
"6426": {
"id": 6426,
"name": "Dombra",
"diamondCost": 20,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/ccd9fea1988521d1e81051a916800d6c~tplv-obj.png"
},
"6427": {
"id": 6427,
"name": "Hat and Mustache",
@@ -2189,6 +2213,12 @@
"diamondCost": 1,
"image": "https://storage.streamdps.com/iblock/e11/e110e47562d77ab5fa26cc31e840f801/a4a1823ef2c1bc65c4dc2a4e82ec446b.png"
},
"6452": {
"id": 6452,
"name": "Jakarta Roundabout",
"diamondCost": 16999,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/31f67910fc5858cf087da65746f1f9f3~tplv-obj.png"
},
"6454": {
"id": 6454,
"name": "",
@@ -2561,6 +2591,12 @@
"diamondCost": 99,
"image": "https://storage.streamdps.com/iblock/b17/b171f313a2ba4af15e8645f02d24d4e7/a58831515ce8102cb72841e8ed45ed08.webp"
},
"6633": {
"id": 6633,
"name": "Independence Day",
"diamondCost": 10,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/b967993872a6e40f3477d30545f8d2eb~tplv-obj.png"
},
"6634": {
"id": 6634,
"name": "Sunday Roast",
@@ -2597,6 +2633,12 @@
"diamondCost": 1,
"image": "https://storage.streamdps.com/iblock/c1e/c1e1683273f5505462f45358cd773806/ba5afaec3f62e18672bd1b25a4e8a4d2.webp"
},
"6655": {
"id": 6655,
"name": "Summer Iris ",
"diamondCost": 30,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/cb591f5b5729fa6e64cac57c78724981~tplv-obj.png"
},
"6661": {
"id": 6661,
"name": "Kiss your Heart",
@@ -2705,6 +2747,12 @@
"diamondCost": 100,
"image": "https://storage.streamdps.com/iblock/cc8/cc8133c73d5ca2cb5fde306f5b4e2a11/fb273956755fe6fbf7263023a9c36ebe.webp"
},
"6741": {
"id": 6741,
"name": "Gorgeous Trophy",
"diamondCost": 7000,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/279c9495c2150e333bc4bc13761d177e~tplv-obj.png"
},
"6743": {
"id": 6743,
"name": "",
@@ -2735,6 +2783,12 @@
"diamondCost": 1,
"image": "https://storage.streamdps.com/iblock/457/457a95d19f48aa254b10a30ee8e2c791/b3e217a47d1b38fe06ef11e149aae0e0.webp"
},
"6756": {
"id": 6756,
"name": "Hot",
"diamondCost": 10,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/ec679890070187b61620b9662afb814e~tplv-obj.png"
},
"6757": {
"id": 6757,
"name": "Dash",
@@ -2837,6 +2891,12 @@
"diamondCost": 199,
"image": "https://storage.streamdps.com/iblock/53b/53ba63daed395b1c8eda6cd5fa51b912/2ddcbdeeebee2a97780b4e846aace552.webp"
},
"6800": {
"id": 6800,
"name": "Pinata",
"diamondCost": 699,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/c8a18d43dc9fb4598d7e991ebeb958ae~tplv-obj.png"
},
"6813": {
"id": 6813,
"name": "Fantastic",
@@ -3129,7 +3189,7 @@
"id": 6967,
"name": "Autumn Leaves",
"diamondCost": 500,
"image": "https://storage.streamdps.com/iblock/f04/f042339687e8abaa2fc0e1976d9b11f4/251a0624bc3a23ba39d75467868dcbf8.webp"
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/30adcaf443df63e3bfd2751ad251f87d~tplv-obj.png"
},
"6968": {
"id": 6968,
@@ -3275,6 +3335,12 @@
"diamondCost": 10,
"image": "https://storage.streamdps.com/iblock/eeb/eeb4c2ae379b89120c8ae69bbbea8642/ea6d73fcf39771c592560120d2c9b577.webp"
},
"7032": {
"id": 7032,
"name": "Maracas",
"diamondCost": 1,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/00204efcf0573192ad5d872c7beeaf5b~tplv-obj.png"
},
"7037": {
"id": 7037,
"name": "",
@@ -3385,9 +3451,9 @@
},
"7084": {
"id": 7084,
"name": "",
"name": "Witchy Kitty",
"diamondCost": 30,
"image": "https://storage.streamdps.com/iblock/1c6/1c64bc1b780d8a987986694a699e0585/0d6afd7934d363dba4a747f8c0bec66b.webp"
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/dfce46f99a1206cca84f9092603e4783~tplv-obj.png"
},
"7086": {
"id": 7086,
@@ -3443,6 +3509,12 @@
"diamondCost": 5,
"image": "https://storage.streamdps.com/iblock/5f2/5f2f2f673c116c46372ee874bfb66685/759b52ad2866ed2f5a78ccd558f4457e.webp"
},
"7105": {
"id": 7105,
"name": "Magic Potion",
"diamondCost": 499,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/e055625e9239df7e833702c768e033d2~tplv-obj.png"
},
"7106": {
"id": 7106,
"name": "",
@@ -3737,6 +3809,12 @@
"diamondCost": 1,
"image": "https://storage.streamdps.com/iblock/bf8/bf8e32bd0bf5a916195b5848db571f4f/a9241b01b970aa44c2d5cea5e8ba69d6.webp"
},
"7377": {
"id": 7377,
"name": "Christmas Market G",
"diamondCost": 2000,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/f498f29ef628c8318006a9ff2f49bf08~tplv-obj.png"
},
"7391": {
"id": 7391,
"name": "I\u0027m shy",
@@ -3773,6 +3851,12 @@
"diamondCost": 10,
"image": "https://storage.streamdps.com/iblock/8f7/8f7fcac557fb70fa0bbd69809c112c0e/978e0bcdcd8cbf4cbdfdb53d9fc39eaa.webp"
},
"7458": {
"id": 7458,
"name": "Wooly Hat",
"diamondCost": 199,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/a234d0187047fa48805c8ea2e1f1f756~tplv-obj.png"
},
"7467": {
"id": 7467,
"name": "Chasing the Dream",
@@ -3785,12 +3869,30 @@
"diamondCost": 5000,
"image": "https://storage.streamdps.com/iblock/f5c/f5cda80a1f9853c49226a450faf26e8f/6318d17d7a2526f521123402d19a4c3e.webp"
},
"7475": {
"id": 7475,
"name": "Mistletoe GDM 23",
"diamondCost": 199,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/3527969b8c27e3194e61ff0787a9c3c2~tplv-obj.png"
},
"7477": {
"id": 7477,
"name": "Panettone GDM 23",
"diamondCost": 1,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/64ce2413a362442819b4551703b7b26c~tplv-obj.png"
},
"7482": {
"id": 7482,
"name": "Flying Jets",
"diamondCost": 5000,
"image": "https://storage.streamdps.com/iblock/5a4/5a4f3c7adc31f60326e3adf1a3a20bf9/bc96de02ceba4b91c1f9c996293974b4.webp"
},
"7498": {
"id": 7498,
"name": "Candy Cane Gun",
"diamondCost": 799,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/58ef7964e32adc5fc47c5706a02e4ff0~tplv-obj.png"
},
"7501": {
"id": 7501,
"name": "Hello Traveler",
@@ -3803,6 +3905,30 @@
"diamondCost": 299,
"image": "https://storage.streamdps.com/iblock/679/679e9e38e38f8a4ce707328aa92b8997/9fedb22710af980bbf7b3092b08ecb94.webp"
},
"7504": {
"id": 7504,
"name": "Holiday Stocking",
"diamondCost": 5,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/e05de50999ebb446e15c4947b30d3140~tplv-obj.png"
},
"7523": {
"id": 7523,
"name": "Hot Choco GDM 23",
"diamondCost": 30,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/f62f5912077d9af84256de288399125a~tplv-obj.png"
},
"7525": {
"id": 7525,
"name": "Christmas CarouseG",
"diamondCost": 2000,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/b5ba3941f7389da7495b659e888ea61a~tplv-obj.png"
},
"7527": {
"id": 7527,
"name": "Christmas Wreath G",
"diamondCost": 10,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/7842b50135e089334fc40d9705bb53c7~tplv-obj.png"
},
"7529": {
"id": 7529,
"name": "Mystery Firework",
@@ -3845,6 +3971,12 @@
"diamondCost": 199,
"image": "https://storage.streamdps.com/iblock/e36/e36cb42edac42a0636f8785b80c274dc/bbd87fef7b269564a4ca32009879c0ab.webp"
},
"7551": {
"id": 7551,
"name": "Snowman",
"diamondCost": 99,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/e094e0fafc14aaf127fa0d0a7926619a~tplv-obj.png"
},
"7591": {
"id": 7591,
"name": "Tiny Diny",
@@ -3899,6 +4031,18 @@
"diamondCost": 500,
"image": "https://storage.streamdps.com/iblock/dca/dcac97e4190d46d113f4bdf2918ee173/4fae166b3f3273b9dbbc2a86bea0ec18.webp"
},
"7697": {
"id": 7697,
"name": "LOVE U",
"diamondCost": 899,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/79d45877691333e2ba69a9098406e95c~tplv-obj.png"
},
"7707": {
"id": 7707,
"name": "I\u0027m blue",
"diamondCost": 5,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/c560ec76d5599198aaea9377c5ffab6e~tplv-obj.png"
},
"7720": {
"id": 7720,
"name": "Flying Jets",
@@ -4013,6 +4157,12 @@
"diamondCost": 199,
"image": "https://storage.streamdps.com/iblock/3c4/3c40e306064bb00719cb77dec261891a/bc24ae9f8a52d13475897170332d546f.webp"
},
"7846": {
"id": 7846,
"name": "Grumpy Glasses",
"diamondCost": 99,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/6f38f8ed7442f69a105788b5c0c74a38~tplv-obj.png"
},
"7851": {
"id": 7851,
"name": "Football Helmet",
@@ -4073,6 +4223,18 @@
"diamondCost": 15000,
"image": "https://storage.streamdps.com/iblock/a12/a12a1b23f1f6a19d728de84e1f43e21d/ff288346e9855a9bb6deb4450491028f.webp"
},
"7920": {
"id": 7920,
"name": "Husky",
"diamondCost": 299,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/a2f5d595e9d96aec19a7c0ed5fa9b017~tplv-obj.png"
},
"7921": {
"id": 7921,
"name": "Golden",
"diamondCost": 299,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/b97f58dcb0250489ae98529bcb0542ca~tplv-obj.png"
},
"7927": {
"id": 7927,
"name": "Puppy Love",
@@ -4157,6 +4319,12 @@
"diamondCost": 10,
"image": "https://storage.streamdps.com/iblock/486/486a2490c987c2bb97b6068fd5aac5ab/49d9045fcfe94bbfbd08c3363bb4512a.webp"
},
"8005": {
"id": 8005,
"name": "Falling For You",
"diamondCost": 299,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/a198bd39d2511dbba6a68867740e3ff9~tplv-obj.png"
},
"8017": {
"id": 8017,
"name": "Headphone",
@@ -4289,6 +4457,12 @@
"diamondCost": 5,
"image": "https://storage.streamdps.com/iblock/27f/27fe97cb0f1b8056fa0aedc918ea560e/966df61a39e36fe0f7237e55c3298117.webp"
},
"8207": {
"id": 8207,
"name": "The Crown",
"diamondCost": 199,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/5bf798f92fe96ba53c0f4d28f052f9bb~tplv-obj.png"
},
"8217": {
"id": 8217,
"name": "Capybara",
@@ -4355,6 +4529,18 @@
"diamondCost": 19999,
"image": "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/a598ba4c7024f4d46c1268be4d82f901~tplv-obj.jpg"
},
"8250": {
"id": 8250,
"name": "Disco ball",
"diamondCost": 1000,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/a53d3ef956eb2f1aa7a7db46024c70bb~tplv-obj.png"
},
"8253": {
"id": 8253,
"name": "Spring Train",
"diamondCost": 3999,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/b859c413a241fec75bc78668aeb0f581~tplv-obj.png"
},
"8259": {
"id": 8259,
"name": "I love you",
@@ -4373,6 +4559,24 @@
"diamondCost": 6000,
"image": "https://storage.streamdps.com/iblock/88d/88df4387d65bcc77b691098fd649bd59/ad401a92ddba9aae15bb777f9f38638d.webp"
},
"8264": {
"id": 8264,
"name": "Happy Weekend",
"diamondCost": 599,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/b04f104e717798235cd3edaa6703e6a3~tplv-obj.png"
},
"8265": {
"id": 8265,
"name": "Happy Friday",
"diamondCost": 399,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/65e8fcb76825b9ec36a24faf9a3e9495~tplv-obj.png"
},
"8266": {
"id": 8266,
"name": "Good Afternoon",
"diamondCost": 399,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/bff3b908c4dd9cf19ab431cc99dc7940~tplv-obj.png"
},
"8267": {
"id": 8267,
"name": "Good Evening",
@@ -4385,6 +4589,12 @@
"diamondCost": 399,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/b7b55087141bd5f965eb31a99a5f157b~tplv-obj.png"
},
"8269": {
"id": 8269,
"name": "Good Morning",
"diamondCost": 399,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/5c1a28f3aa7eefc27491f3020748ce54~tplv-obj.png"
},
"8277": {
"id": 8277,
"name": "Love Drop",
@@ -4529,6 +4739,12 @@
"diamondCost": 15000,
"image": "https://storage.streamdps.com/iblock/bfc/bfcf491b940e478b6410047bc047af1b/abbbdd13015a9f31be1b905268873d73.webp"
},
"8442": {
"id": 8442,
"name": "Flower Festival",
"diamondCost": 199,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/9bfe63e39b581a69ff944758c3eae5a0~tplv-obj.png"
},
"8448": {
"id": 8448,
"name": "Raccoon",
@@ -4667,6 +4883,12 @@
"diamondCost": 199,
"image": "https://storage.streamdps.com/iblock/282/28210d38f8625d7c3f39bb85e1fd59c4/61f609f602fc0f3cc2bff6cf94d32fbe.webp"
},
"8754": {
"id": 8754,
"name": "Aurora",
"diamondCost": 12000,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/1f59f5593ce135325c1a034825cec18c.png~tplv-obj.png"
},
"8767": {
"id": 8767,
"name": "Golden Trumpet",
@@ -4691,6 +4913,18 @@
"diamondCost": 450,
"image": "https://storage.streamdps.com/iblock/285/285070af9d4f72b74e7d74c22157f2d9/67d9fa3239a7f9a09ef78c832a66e624.webp"
},
"8803": {
"id": 8803,
"name": "Miss You",
"diamondCost": 1,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/3c53396b922691a7520698f47105a753.png~tplv-obj.png"
},
"8804": {
"id": 8804,
"name": "Vacation",
"diamondCost": 1,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/8f46e8eef9cbd5304fb802104c2b4ef4.png~tplv-obj.png"
},
"8806": {
"id": 8806,
"name": "Dancing Capybaras",
@@ -4757,6 +4991,12 @@
"diamondCost": 1200,
"image": "https://storage.streamdps.com/iblock/07d/07d678346c7eb588bc3cbddf343ab791/8f8f50f5350e4b1c0b151aff333e43a4.webp"
},
"8890": {
"id": 8890,
"name": "Pink Shoes",
"diamondCost": 5,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/cba8a7c718988bd51c7b6055e9ab1ec4.png~tplv-obj.png"
},
"8892": {
"id": 8892,
"name": "Dream Team",
@@ -4859,6 +5099,12 @@
"diamondCost": 1,
"image": "https://storage.streamdps.com/iblock/5b9/5b9eca4a99e965cb25183681a07a5276/c28f7e9c4a8e42460225ff2d12300ae7.webp"
},
"9111": {
"id": 9111,
"name": "Popcorn",
"diamondCost": 5,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/676d2d4c31a8979f1fd06cdf5ecd922f~tplv-obj.png"
},
"9135": {
"id": 9135,
"name": "Magic Forest",
@@ -4895,6 +5141,12 @@
"diamondCost": 3999,
"image": "https://storage.streamdps.com/iblock/990/99093308d3ad2c3833d230f9aae7f702/36f073ad050bd79d7d1ba6d781e7ef11.webp"
},
"9152": {
"id": 9152,
"name": "Spin with me GDM",
"diamondCost": 199,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/149ac2e87d05490d7d251149cefe27a2.png~tplv-obj.png"
},
"9175": {
"id": 9175,
"name": "Pretzel",
@@ -4913,12 +5165,30 @@
"diamondCost": 20000,
"image": "https://storage.streamdps.com/iblock/c79/c793af446369ecef5238e73312c84ccd/464a76f3e6eaee9afc771f45a4bba9df.webp"
},
"9242": {
"id": 9242,
"name": "Pumpkin Spice Latte",
"diamondCost": 10,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/0636d91615f7417ddd5f29438bf5debe~tplv-obj.png"
},
"9255": {
"id": 9255,
"name": "Aerobic headband",
"diamondCost": 99,
"image": "https://storage.streamdps.com/iblock/3d9/3d98c2fbc96922da37a9d22881bb06b9/0a99af132ab8e3fe9806d2412abc6bf0.webp"
},
"9303": {
"id": 9303,
"name": "Rabbit and Mochi",
"diamondCost": 999,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/213ef2549fbb10ec783c95a41d28cf0a.png~tplv-obj.png"
},
"9304": {
"id": 9304,
"name": "Boo the Ghost",
"diamondCost": 88,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/cb909c78f2412e4927ea68d6af8e048f.png~tplv-obj.png"
},
"9333": {
"id": 9333,
"name": "LIVE Fest Clappers",
@@ -4931,6 +5201,30 @@
"diamondCost": 1,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/1e98afffef90ed4b2cc9c9ebb88e3608.png~tplv-obj.png"
},
"9354": {
"id": 9354,
"name": "I\u0027m here",
"diamondCost": 1,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/7006392a82d57452d5ef08dd90e169c1.png~tplv-obj.png"
},
"9355": {
"id": 9355,
"name": "So cute",
"diamondCost": 1,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/d40d31241efcf57c630e894bb3007b8a.png~tplv-obj.png"
},
"9363": {
"id": 9363,
"name": "Elf GDM 23",
"diamondCost": 1,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/60e5289b379660cc562742cf987a2d35.png~tplv-obj.png"
},
"9427": {
"id": 9427,
"name": "Pegasus",
"diamondCost": 42999,
"image": "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/f600a2495ab5d250e7da2066484a9383.png~tplv-obj.jpg"
},
"9463": {
"id": 9463,
"name": "Fairy Wings",
@@ -5003,22 +5297,232 @@
"diamondCost": 12000,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/51a7d74bcb4a6417be59f0ffc0b77e96.png~tplv-obj.png"
},
"9522": {
"id": 9522,
"name": "Exclusive Spark",
"diamondCost": 1000,
"image": "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/f0bda1eb6856e2feea9cfcb6c575c8a0.png~tplv-obj.jpg"
"9535": {
"id": 9535,
"name": "Play for you",
"diamondCost": 299,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/182659e90a3432aa155e61c9c0d89df0.png~tplv-obj.png"
},
"9523": {
"id": 9523,
"name": "Exclusive Jet",
"9536": {
"id": 9536,
"name": "Fake smile",
"diamondCost": 299,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/35ce62173962e33834703212d0b845a7.png~tplv-obj.png"
},
"9576": {
"id": 9576,
"name": "Yeah Nah",
"diamondCost": 1,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/4b20c5aab3841657a343be3769307805.png~tplv-obj.png"
},
"9581": {
"id": 9581,
"name": "Turkey Face GDDec",
"diamondCost": 399,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/65349d1ef783fc207c1d2b54a8d521a7.png~tplv-obj.png"
},
"9583": {
"id": 9583,
"name": "Cool!",
"diamondCost": 1,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/424c61f16c16919f169fd0352bd24661.png~tplv-obj.png"
},
"9587": {
"id": 9587,
"name": "Christmas Potato",
"diamondCost": 10,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/5448f1f5157d3a4a88e0f57acf3dbfe0.png~tplv-obj.png"
},
"9604": {
"id": 9604,
"name": "Gobble Gobble",
"diamondCost": 1,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/ada9babc0b55cf005e8c8d13dfc30b42.png~tplv-obj.png"
},
"9615": {
"id": 9615,
"name": "Festive Tiny Diny",
"diamondCost": 15,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/f2a8c2967c7153e9077bb469f2e42317.png~tplv-obj.png"
},
"9617": {
"id": 9617,
"name": "Xmas Mishka Bear",
"diamondCost": 199,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/700c1c8817847317407cc2b8c6c9da42.png~tplv-obj.png"
},
"9625": {
"id": 9625,
"name": "Elf\u0027s Hat ",
"diamondCost": 299,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/f9857a040c92b34d6a261201a93c185f.png~tplv-obj.png"
},
"9639": {
"id": 9639,
"name": "2024",
"diamondCost": 1,
"image": "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/484a44bbe62ce47687d1da31a6602bbd.png~tplv-obj.jpg"
},
"9640": {
"id": 9640,
"name": "2024 Glasses",
"diamondCost": 224,
"image": "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/512d2e9934cbae8d1019a391814edbd2.png~tplv-obj.jpg"
},
"9641": {
"id": 9641,
"name": "2024 Countdown",
"diamondCost": 2024,
"image": "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/a358a3ce18241dcc6e7d0b02d091d563.png~tplv-obj.jpg"
},
"9642": {
"id": 9642,
"name": "Train to 2024",
"diamondCost": 12024,
"image": "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/0cb12913e0a96bbcc45f97b450e74cd3.png~tplv-obj.jpg"
},
"9643": {
"id": 9643,
"name": "2024 JoyLens",
"diamondCost": 224,
"image": "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/36d82bbcd87c1914df84262d9bdd9b95.png~tplv-obj.jpg"
},
"9644": {
"id": 9644,
"name": "Sparkling Countdown",
"diamondCost": 2024,
"image": "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/d6b4539ed9683707bdefe268f6575e74.png~tplv-obj.jpg"
},
"9645": {
"id": 9645,
"name": "New Year Journey",
"diamondCost": 12024,
"image": "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/0ef9654d92354172fb9b3b364827940c.png~tplv-obj.jpg"
},
"9647": {
"id": 9647,
"name": "Kitten Paw",
"diamondCost": 299,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/332520d7b5085ce591396c8d2bb9d352.png~tplv-obj.png"
},
"9650": {
"id": 9650,
"name": "The Van Cat",
"diamondCost": 799,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/6973dd1b6d3dee3ca3f0ebac3c1d2977.png~tplv-obj.png"
},
"9656": {
"id": 9656,
"name": "Gingerbread man",
"diamondCost": 5,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/af01db3e3cb9f54ea2cb421fab6062bc.png~tplv-obj.png"
},
"9657": {
"id": 9657,
"name": "GB North Pole",
"diamondCost": 199,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/79715a53c41619e7b205eb26e57926d4.png~tplv-obj.png"
},
"9658": {
"id": 9658,
"name": "DE North Pole",
"diamondCost": 199,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/130e17b5b561a93cefbd236586881477.png~tplv-obj.png"
},
"9667": {
"id": 9667,
"name": "Kiwi Bird",
"diamondCost": 10,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/b73cb4aaa76a33efd881192589d65351.png~tplv-obj.png"
},
"9668": {
"id": 9668,
"name": "Gingerman Party",
"diamondCost": 1200,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/008a9554e736642f1b2dca9f198bb710.png~tplv-obj.png"
},
"9670": {
"id": 9670,
"name": "Reindeer",
"diamondCost": 299,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/4565fa0cd1dbf76463144b0d4cc50bf1.png~tplv-obj.png"
},
"9671": {
"id": 9671,
"name": "Gingebread Man",
"diamondCost": 5,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/2399f65414f77419ec7d5e9274dc8e0e.png~tplv-obj.png"
},
"9672": {
"id": 9672,
"name": "Mimi \u0026 Fifi",
"diamondCost": 5000,
"image": "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/1cc167a00aa4d5dfe48018afb38c3daa.png~tplv-obj.jpg"
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/0a72d0084695d03586fea7d854dc3a47.png~tplv-obj.png"
},
"9524": {
"id": 9524,
"name": "Exclusive Yacht",
"9678": {
"id": 9678,
"name": "Holiday Carousel",
"diamondCost": 2000,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/b5ba3941f7389da7495b659e888ea61a.png~tplv-obj.png"
},
"9680": {
"id": 9680,
"name": "Xmas in London",
"diamondCost": 20000,
"image": "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/da8c85d5ae09ebf320216202e8fa015d.png~tplv-obj.jpg"
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/876204a6ad0b1b0e4675d9be42439183.png~tplv-obj.png"
},
"9682": {
"id": 9682,
"name": "Stay Warm",
"diamondCost": 450,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/abd104eb08ce0c351292036d8897fb8d.png~tplv-obj.png"
},
"9688": {
"id": 9688,
"name": "Snowglobe",
"diamondCost": 499,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/ea5ac5f8e186897456bed2e78fc78ca5.png~tplv-obj.png"
},
"9698": {
"id": 9698,
"name": "Candy Cane",
"diamondCost": 1,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/1fa0a4ed666304c78a46de200b85c84b.png~tplv-obj.png"
},
"9703": {
"id": 9703,
"name": "Really Curious",
"diamondCost": 1,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/793ba68723567b695b12f2ef08dc1484.png~tplv-obj.png"
},
"9704": {
"id": 9704,
"name": "Nemo",
"diamondCost": 15,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/68fcf30cb3fb07e9546f5e7fbc2b0ac0.png~tplv-obj.png"
},
"9706": {
"id": 9706,
"name": "Elfs Hat ",
"diamondCost": 299,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/f9857a040c92b34d6a261201a93c185f.png~tplv-obj.png"
},
"9717": {
"id": 9717,
"name": "Lucky Airdrop Box",
"diamondCost": 999,
"image": "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/6ae56f08ae3ee57ea2dda0025bfd39d3.png~tplv-obj.jpg"
},
"9770": {
"id": 9770,
"name": "Shiba Cookie",
"diamondCost": 10,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/4ea5282e7f61cbeee1214422d40ad407.png~tplv-obj.png"
},
"9771": {
"id": 9771,
"name": "KFC Chicken",
"diamondCost": 5,
"image": "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/f9d59ccd2328b8a46841b3b1c87d9e55.png~tplv-obj.png"
}
}

Binary file not shown.

View File

@@ -0,0 +1,64 @@
Collects live data to mongodb database
```xml
<repositories>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>com.github.jwdeveloper.TikTok-Live-Java</groupId>
<artifactId>Client</artifactId>
<version>1.1.0-Release</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.github.jwdeveloper.TikTok-Live-Java</groupId>
<artifactId>extension-collector</artifactId>
<version>1.1.0-Release</version>
</dependency>
</dependencies>
```
Usage
```java
public static void main(String[] args) throws IOException {
var collector = TikTokLiveCollector.use(settings ->
{
settings.setConnectionUrl("mongodb+srv://" + mongoUser + ":" + mongoPassword + "@" + mongoDatabase + "/?retryWrites=true&w=majority");
settings.setDatabaseName("tiktok");
});
collector.connectDatabase();
var users = List.of("tehila_723", "dino123597", "domaxyzx", "dash4214", "obserwacje_live");
var sessionTag = "Tag1";
for (var user : users) {
TikTokLive.newClient(user)
.configure(liveClientSettings ->
{
liveClientSettings.setPrintToConsole(true);
})
.onError((liveClient, event) ->
{
event.getException().printStackTrace();
})
.addListener(collector.newListener(Map.of("sessionTag", sessionTag), document ->
{
if (document.get("dataType") == "message") {
return false;
}
return true;
}))
.buildAndConnectAsync();
}
System.in.read();
collector.disconnectDatabase();
}
```

View File

@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>io.github.jwdeveloper.tiktok</groupId>
<artifactId>TikTokLiveJava</artifactId>
<version>1.1.0-Release</version>
</parent>
<artifactId>extension-collector</artifactId>
<dependencies>
<dependency>
<groupId>io.github.jwdeveloper.tiktok</groupId>
<artifactId>API</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>3.24.1</version>
</dependency>
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongodb-driver-sync</artifactId>
<version>4.4.0</version>
</dependency>
<dependency>
<groupId>io.github.jwdeveloper.tiktok</groupId>
<artifactId>API</artifactId>
<version>1.1.0-Release</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>

Some files were not shown because too many files have changed in this diff Show More