Compare commits

...

74 Commits

Author SHA1 Message Date
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
JW
34a78b5435 Including toUser in TikTokGiftEvent 2023-12-19 21:49:27 +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
JW
0bb8edfe5c Including toUser in TikTokGiftEvent 2023-12-19 21:31:58 +01:00
JW
4979c1b27a Merge remote-tracking branch 'origin/master' 2023-12-19 21:30:31 +01:00
Jacek W
f7c8ffdaa5 Update README.md 2023-12-19 17:34:07 +01:00
GitHub Action
c1fda687d3 Update version in pom.xml 2023-12-19 16:31:06 +00:00
JW
05c49c4545 . 2023-12-19 17:29:16 +01:00
Jacek W
7d36f36cee Update README.md 2023-12-19 14:01:48 +01:00
GitHub Action
f8a716429d Update version in pom.xml 2023-12-19 12:58:59 +00:00
Jacek W
fee805f0ea Merge pull request #35 from jwdeveloper/develop-1-0-10
Develop 1 0 10
2023-12-19 13:57:08 +01:00
JW
1733102aff Changes:
- TikTokHttpResponseEvent
  - Fixed User attributes in CommentEvent
  - Redesign .onMapper method
2023-12-19 13:56:25 +01:00
JW
89f54d5976 Merge branch 'master' into develop-1-0-10 2023-12-19 13:56:19 +01:00
JW
39055b5f3a Merge branch 'master' into develop-1-0-10 2023-12-19 13:54:50 +01:00
Jacek W
cd9e9ead85 Merge pull request #34 from jwdeveloper/develop-1-0-10
Develop 1 0 10
2023-12-19 13:48:47 +01:00
JW
5a2d2d23f0 Changes:
- TikTokHttpResponseEvent
  - Fixed User attributes in CommentEvent
  - Redesign .onMapper method
2023-12-19 13:46:11 +01:00
JW
3eed982d6b Changes:
- TikTokHttpResponseEvent
  - Fixed User attributes in CommentEvent
  - Redesign .onMapper method
2023-12-19 04:01:06 +01:00
GitHub Action
385560de3a Update version in pom.xml 2023-12-18 21:58:15 +00:00
Jacek W
2e37b6627b Merge pull request #33 from kohlerpop1/kohlerpop1-fixes/updates
Updates to TikTokGiftEvent & grammar fixes!
2023-12-18 22:56:33 +01:00
kohlerpop1
c4f0d63b43 Add toUser to TikTokGiftEvent to determine the correct user receiving a gift!
Made grammar fix to collaboration.md and README.md!
2023-12-18 16:52:40 -05:00
Jacek W
15550ed703 Update README.md 2023-12-12 19:49:52 +01:00
GitHub Action
c7d84218f2 Update version in pom.xml 2023-12-12 18:49:13 +00:00
Jacek W
1ecd192539 Merge pull request #30 from jwdeveloper/develop-1-0-8
updated structure of WebcastLinkLayerMessage
in proto file
2023-12-12 19:47:35 +01:00
JW
d6c0d50ac3 Changes:
updated structure of WebcastLinkLayerMessage
  in proto file
2023-12-12 19:46:37 +01:00
Jacek W
b4997da0a8 Merge pull request #29 from jwdeveloper/develop-1-0-8
updated structure of WebcastLinkLayerMessage
in proto file
2023-12-12 19:39:51 +01:00
JW
82990665b8 Changes:
updated structure of WebcastLinkLayerMessage
  in proto file
2023-12-12 19:38:55 +01:00
Jacek W
155b47977d Update README.md 2023-12-12 13:04:51 +01:00
Jacek W
3e262773a4 Update README.md 2023-12-11 23:37:30 +01:00
GitHub Action
89fbeb848b Update version in pom.xml 2023-12-11 22:28:01 +00:00
JW
aa99c5929b Changes:
onCustomEvent() <- registering custom events
  onMapping() <-  custom mappings
  check out 'CustomMappingExample'

  more gifs has been added
  exceptions are more explicit
2023-12-11 23:24:52 +01:00
JW
4dd866c6cc Changes:
onCustomEvent() <- registering custom events
  onMapping() <-  custom mappings
  check out 'CustomMappingExample'

  more gifs has been added
  exceptions are more explicit
2023-12-11 23:22:38 +01:00
Jacek W
0d384f0fdc Merge pull request #28 from jwdeveloper/develop-1-0-7
Develop 1 0 7
2023-12-11 23:20:03 +01:00
JW
3832db111e Changes:
onCustomEvent() <- registering custom events
  onMapping() <-  custom mappings
  check out 'CustomMappingExample'

  more gifs has been added
  exceptions are more explicit
2023-12-11 23:18:40 +01:00
JW
46d5f15d3f . 2023-12-11 03:56:17 +01:00
Jacek W
4f3ec1c6d9 Update collaboration.md 2023-12-05 02:56:53 +01:00
Jacek W
31075e5f09 Update collaboration.md 2023-12-05 02:55:32 +01:00
Jacek W
33a3f7afb8 Update collaboration.md 2023-12-05 02:33:33 +01:00
Jacek W
6c888c5d5b Update collaboration.md 2023-12-05 02:32:42 +01:00
Jacek W
214aa3b1ff Update collaboration.md 2023-12-05 02:30:39 +01:00
Jacek W
3f2c9083d5 Update README.md 2023-12-05 02:24:25 +01:00
Jacek W
450014759c Update collaboration.md 2023-12-05 02:17:50 +01:00
Jacek W
790f568244 Update collaboration.md 2023-12-05 02:13:43 +01:00
JW
e3e0d8a88e Merge remote-tracking branch 'origin/master' 2023-12-05 01:43:34 +01:00
JW
e0136d0f3b Collaboration Guide 2023-12-05 01:43:26 +01:00
Jacek W
dd5ccbfb7f Update README.md 2023-11-14 18:48:58 +01:00
GitHub Action
110a4fab4c Update version in pom.xml 2023-11-14 17:48:13 +00:00
Jacek W
69a2a6a338 Merge pull request #18 from jwdeveloper/develop-1-0-6
Changes:
  fixed TikTokLive.isHostNameValid()
  fixed TikTokLive.isLiveOnline()
  
  those methods was not working in some regions
2023-11-14 18:46:28 +01:00
JW
530551763c Changes:
fixed TikTokLive.isHostNameValid()
  fixed TikTokLive.isLiveOnline()

  those methods was not working in some regions
2023-11-14 18:44:09 +01:00
GitHub Action
ca1827853a Update version in pom.xml 2023-11-13 23:09:58 +00:00
Jacek W
ea8c740faa Merge pull request #17 from jwdeveloper/develop-1-0-5
New Events
- onLiveUnpaused()
- onRoomInfo() triggered when LiveRoomInfo got updated

Removed:
- clientSettings.setHandleExistingEvents - onRoom Replaced with onRoomInfo event - onRoomUserInfo Replaced with onRoomInfo event

Gifts:
- onGift event was not triggered for the more expensive gifts
- onGiftCombo with more expensive gifts was stuck in the GiftSendType.Begin state

Fixed:
- setPrintToConsole(false) was not disabling logs
2023-11-14 00:08:34 +01:00
JW
1977cbe8dc Changes:
New Events
     -  onLiveUnpaused()
     -  onRoomInfo() triggered when LiveRoomInfo got updated

  Removed:
      - clientSettings.setHandleExistingEvents
      - onRoom  Replaced with onRoomInfo event
      - onRoomUserInfo  Replaced with onRoomInfo event

  Gifts:
    - onGift event was not triggered for the more expensive gifts
    - onGiftCombo with more expensive gifts was stuck in the GiftSendType.Begin state

  Fixed:
     - setPrintToConsole(false) was not disabling logs
2023-11-14 00:07:14 +01:00
Jacek W
c3a48c4d70 Update README.md 2023-11-10 22:29:53 +01:00
GitHub Action
2c51844fd9 Update version in pom.xml 2023-11-10 21:29:22 +00:00
195 changed files with 60453 additions and 10995 deletions

4
.gitignore vendored
View File

@@ -1,10 +1,10 @@
backend-infrastructure/.aws-sam backend-infrastructure/.aws-sam
# Created by https://www.gitignore.io/api/osx,linux,python,windows,pycharm,visualstudiocode # Created by https://www.gitignore.io/api/osx,linux,python,windows,pycharm,visualstudiocode
*.db
### Linux ### ### Linux ###
*~ *~
.db
# temporary files which can be created if a process still has a handle open of a deleted file # temporary files which can be created if a process still has a handle open of a deleted file
.fuse_hidden* .fuse_hidden*

View File

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

View File

@@ -1,96 +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 handle Events received from Room when Connecting
*/
private boolean handleExistingEvents;
/**
* Whether to print Logs to Console
*/
private boolean printToConsole;
/**
* 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

@@ -26,7 +26,7 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
public @interface TikTokEventHandler public @interface TikTokEventObserver
{ {
} }

View File

@@ -20,20 +20,17 @@
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ */
package io.github.jwdeveloper.tiktok.live; package io.github.jwdeveloper.tiktok.data.dto;
import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
import lombok.Value;
import java.time.Duration;
@Data @Data
public class LiveRoomMeta { @AllArgsConstructor
public class MessageMetaData
private LiveRoomStatus status; {
private boolean ageRestricted; private Duration handlingTime;
public enum LiveRoomStatus
{
HostNotFound,
HostOnline,
HostOffline
}
} }

View File

@@ -0,0 +1,43 @@
/*
* 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;
import io.github.jwdeveloper.tiktok.annotations.EventMeta;
import io.github.jwdeveloper.tiktok.annotations.EventType;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokHeaderEvent;
import io.github.jwdeveloper.tiktok.data.models.users.User;
import lombok.Data;
@Data
@EventMeta(eventType = EventType.Message)
public class CustomEvent extends TikTokHeaderEvent {
private final User user;
private final String title;
public CustomEvent(User user, String title) {
this.user = user;
this.title = title;
}
}

View File

@@ -47,11 +47,11 @@ public class TikTokCommentEvent extends TikTokHeaderEvent {
public TikTokCommentEvent(WebcastChatMessage msg) { public TikTokCommentEvent(WebcastChatMessage msg) {
super(msg.getCommon()); super(msg.getCommon());
user = User.map(msg.getUser()); user = User.map(msg.getUser(),msg.getUserIdentity());
text = msg.getContent(); text = msg.getContent();
visibleToSender = msg.getVisibleToSender(); visibleToSender = msg.getVisibleToSender();
getUserLanguage = msg.getContentLanguage(); getUserLanguage = msg.getContentLanguage();
mentionedUser = User.map(msg.getAtUser(),msg.getUserIdentity()); mentionedUser = User.map(msg.getAtUser());
pictures = msg.getEmotesListList().stream().map(e -> Picture.map(e.getEmote().getImage())).toList(); pictures = msg.getEmotesListList().stream().map(e -> Picture.map(e.getEmote().getImage())).toList();
} }
} }

View File

@@ -34,18 +34,10 @@ import java.util.List;
@Getter @Getter
@EventMeta(eventType = EventType.Message) @EventMeta(eventType = EventType.Message)
public class TikTokLinkEvent extends TikTokHeaderEvent { public class TikTokLinkEvent extends TikTokHeaderEvent {
private final String token;
private User user;
private final List<User> otherUsers;
public TikTokLinkEvent(WebcastLinkMessage msg) { public TikTokLinkEvent(WebcastLinkMessage msg) {
super(msg.getCommon()); super(msg.getCommon());
token = msg.getToken();
if (msg.getUser().getUser().hasUser()) {
user = new User(msg.getUser().getUser().getUser());
}
otherUsers = msg.getUser().getOtherUsersList().stream().map(e -> new User(e.getUser())).toList();
} }
} }

View File

@@ -20,17 +20,13 @@
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ */
package io.github.jwdeveloper.tiktok.live; 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.TikTokEvent; import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent;
import io.github.jwdeveloper.tiktok.data.models.gifts.Gift;
import io.github.jwdeveloper.tiktok.data.models.users.User;
import java.util.List;
public interface TrackedUser @EventMeta(eventType = EventType.Message)
{ public class TikTokLiveUnpausedEvent extends TikTokEvent {
List<TikTokEvent> getInvokedEvents();
List<Gift> getGifs();
User getUserData();
} }

View File

@@ -26,7 +26,9 @@ import io.github.jwdeveloper.tiktok.annotations.EventMeta;
import io.github.jwdeveloper.tiktok.annotations.EventType; import io.github.jwdeveloper.tiktok.annotations.EventType;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokHeaderEvent; import io.github.jwdeveloper.tiktok.data.events.common.TikTokHeaderEvent;
import io.github.jwdeveloper.tiktok.data.models.users.User; import io.github.jwdeveloper.tiktok.data.models.users.User;
import io.github.jwdeveloper.tiktok.data.models.users.UserAttribute;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastMemberMessage; import io.github.jwdeveloper.tiktok.messages.webcast.WebcastMemberMessage;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastSubNotifyMessage;
import lombok.Getter; import lombok.Getter;
/** /**
@@ -34,16 +36,20 @@ import lombok.Getter;
*/ */
@Getter @Getter
@EventMeta(eventType = EventType.Message) @EventMeta(eventType = EventType.Message)
public class TikTokSubscribeEvent extends TikTokHeaderEvent { public class TikTokSubscribeEvent extends TikTokHeaderEvent
private User user; {
private final User user;
public TikTokSubscribeEvent(WebcastMemberMessage msg) { public TikTokSubscribeEvent(WebcastMemberMessage msg) {
super(msg.getCommon()); super(msg.getCommon());
user = User.map(msg.getUser());
if(msg.hasUser()) user.addAttribute(UserAttribute.Subscriber);
{ }
user = new User(msg.getUser());
public TikTokSubscribeEvent(WebcastSubNotifyMessage msg) {
super(msg.getCommon());
user = User.map(msg.getUser());
user.addAttribute(UserAttribute.Subscriber);
} }
}
} }

View File

@@ -26,6 +26,7 @@ import io.github.jwdeveloper.tiktok.annotations.EventMeta;
import io.github.jwdeveloper.tiktok.annotations.EventType; import io.github.jwdeveloper.tiktok.annotations.EventType;
import io.github.jwdeveloper.tiktok.data.models.gifts.Gift; import io.github.jwdeveloper.tiktok.data.models.gifts.Gift;
import io.github.jwdeveloper.tiktok.data.models.gifts.GiftSendType; import io.github.jwdeveloper.tiktok.data.models.gifts.GiftSendType;
import io.github.jwdeveloper.tiktok.data.models.users.User;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastGiftMessage; import io.github.jwdeveloper.tiktok.messages.webcast.WebcastGiftMessage;
import lombok.Getter; import lombok.Getter;
@@ -40,7 +41,7 @@ import lombok.Getter;
* <p>Combo: 4 -> comboState = GiftSendType.Active</p> * <p>Combo: 4 -> comboState = GiftSendType.Active</p>
* <p>Combo: 8 -> comboState = GiftSendType.Active</p> * <p>Combo: 8 -> comboState = GiftSendType.Active</p>
* <p>Combo: 12 -> comboState = GiftSendType.Finsihed</p> * <p>Combo: 12 -> comboState = GiftSendType.Finsihed</p>
* * <p>
* Remember if comboState is Finsihed both TikTokGiftComboEvent and TikTokGiftEvent event gets triggered * Remember if comboState is Finsihed both TikTokGiftComboEvent and TikTokGiftEvent event gets triggered
*/ */
@EventMeta(eventType = EventType.Message) @EventMeta(eventType = EventType.Message)
@@ -48,8 +49,8 @@ import lombok.Getter;
public class TikTokGiftComboEvent extends TikTokGiftEvent { public class TikTokGiftComboEvent extends TikTokGiftEvent {
private final GiftSendType comboState; private final GiftSendType comboState;
public TikTokGiftComboEvent(Gift gift, WebcastGiftMessage msg, GiftSendType comboState) { public TikTokGiftComboEvent(Gift gift, User host, WebcastGiftMessage msg, GiftSendType comboState) {
super(gift, msg); super(gift, host, msg);
this.comboState = comboState; this.comboState = comboState;
} }
} }

View File

@@ -26,28 +26,38 @@ package io.github.jwdeveloper.tiktok.data.events.gift;
import io.github.jwdeveloper.tiktok.annotations.EventMeta; import io.github.jwdeveloper.tiktok.annotations.EventMeta;
import io.github.jwdeveloper.tiktok.annotations.EventType; import io.github.jwdeveloper.tiktok.annotations.EventType;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokHeaderEvent; import io.github.jwdeveloper.tiktok.data.events.common.TikTokHeaderEvent;
import io.github.jwdeveloper.tiktok.data.models.Picture;
import io.github.jwdeveloper.tiktok.data.models.gifts.Gift; import io.github.jwdeveloper.tiktok.data.models.gifts.Gift;
import io.github.jwdeveloper.tiktok.data.models.gifts.GiftSendType; import io.github.jwdeveloper.tiktok.data.models.gifts.GiftSendType;
import io.github.jwdeveloper.tiktok.data.models.users.User; import io.github.jwdeveloper.tiktok.data.models.users.User;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastGiftMessage; import io.github.jwdeveloper.tiktok.messages.webcast.WebcastGiftMessage;
import lombok.Getter; import lombok.Getter;
import java.util.ArrayList;
/*
/**
* Triggered when user sends gifts that has * Triggered when user sends gifts that has
* no combo (most of expensive gifts) * no combo (most of expensive gifts)
* or if combo has finished * or if combo has finished
*/ */
@EventMeta(eventType = EventType.Message) @EventMeta(eventType = EventType.Message)
@Getter @Getter
public class TikTokGiftEvent extends TikTokHeaderEvent { public class TikTokGiftEvent extends TikTokHeaderEvent {
private final Gift gift; private final Gift gift;
private final User user; private final User user;
private final User toUser;
private final int combo; private final int combo;
public TikTokGiftEvent(Gift gift, WebcastGiftMessage msg) {
public TikTokGiftEvent(Gift gift, User liveHost, WebcastGiftMessage msg) {
super(msg.getCommon()); super(msg.getCommon());
this.gift = gift; this.gift = gift;
user = User.map(msg.getUser(), msg.getUserIdentity()); user = User.map(msg.getUser(), msg.getUserIdentity());
if (msg.getToUser().getNickname().isEmpty()) {
toUser = liveHost;
} else {
toUser = User.map(msg.getToUser());
}
combo = msg.getComboCount(); combo = msg.getComboCount();
} }
} }

View File

@@ -0,0 +1,42 @@
/*
* 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.models.http.HttpData;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
@EventMeta(eventType = EventType.Debug)
public class TikTokHttpResponseEvent extends TikTokEvent
{
String url;
HttpData response;
HttpData request;
}

View File

@@ -1,67 +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.data.events.room;
import io.github.jwdeveloper.tiktok.annotations.EventMeta;
import io.github.jwdeveloper.tiktok.annotations.EventType;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokHeaderEvent;
import io.github.jwdeveloper.tiktok.data.models.users.User;
import io.github.jwdeveloper.tiktok.data.models.users.UserAttribute;
import io.github.jwdeveloper.tiktok.messages.webcast.RoomMessage;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastLiveIntroMessage;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastRoomMessage;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastSystemMessage;
import lombok.Getter;
@Getter
@EventMeta(eventType = EventType.Message)
public class TikTokRoomEvent extends TikTokHeaderEvent
{
private User hostUser;
private String hostLanguage;
private final String welcomeMessage;
public TikTokRoomEvent(WebcastRoomMessage msg) {
super(msg.getCommon());
welcomeMessage = msg.getContent();
}
public TikTokRoomEvent(RoomMessage msg) {
super(msg.getCommon());
welcomeMessage = msg.getContent();
}
public TikTokRoomEvent(WebcastSystemMessage msg) {
super(msg.getCommon());
welcomeMessage = msg.getMessage();
}
public TikTokRoomEvent(WebcastLiveIntroMessage msg) {
super(msg.getCommon());
hostUser = User.map(msg.getHost());
hostUser.addAttribute(UserAttribute.LiveHost);
welcomeMessage = msg.getContent();
hostLanguage = msg.getLanguage();
}
}

View File

@@ -0,0 +1,42 @@
/*
* 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.room;
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.live.LiveRoomInfo;
import lombok.AllArgsConstructor;
import lombok.Getter;
/*
Triggered when LiveRoomInfo got updated such as likes, viewers, ranking ....
*/
@Getter
@AllArgsConstructor
@EventMeta(eventType = EventType.Message)
public class TikTokRoomInfoEvent extends TikTokEvent
{
LiveRoomInfo roomInfo;
}

View File

@@ -24,7 +24,9 @@ package io.github.jwdeveloper.tiktok.data.events.websocket;
import io.github.jwdeveloper.tiktok.annotations.EventMeta; import io.github.jwdeveloper.tiktok.annotations.EventMeta;
import io.github.jwdeveloper.tiktok.annotations.EventType; import io.github.jwdeveloper.tiktok.annotations.EventType;
import io.github.jwdeveloper.tiktok.data.dto.MessageMetaData;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent; import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastGiftMessage;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastResponse; import io.github.jwdeveloper.tiktok.messages.webcast.WebcastResponse;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
@@ -34,22 +36,32 @@ import java.time.Duration;
/** /**
* 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.
*/ */
@Getter @Getter
@AllArgsConstructor @AllArgsConstructor
@EventMeta(eventType = EventType.Debug) @EventMeta(eventType = EventType.Debug)
public class TikTokWebsocketMessageEvent extends TikTokEvent public class TikTokWebsocketMessageEvent extends TikTokEvent {
{
private TikTokEvent event;
/*
* Original message that is coming from TikTok
* message.method - Name of message type, for example "WebcastGiftMessage"
* message.payload - Bytes array that contains actual data of message.
* Example of parsing, WebcastGiftMessage giftMessage = WebcastGiftMessage.parseFrom(message.getPayload());
*/
private WebcastResponse.Message message; private WebcastResponse.Message message;
private MetaData metaData; /*
* TikTokLiveJava event that was created from TikTok message data
* Example: TikTokGiftEvent
*/
private TikTokEvent event;
/*
* Metadata information about mapping message to event, such as time and stuff
*/
private MessageMetaData metaData;
@Value
public static class MetaData
{
Duration handlingTime;
}
} }

View File

@@ -39,4 +39,9 @@ public class TikTokWebsocketUnhandledMessageEvent extends TikTokUnhandledEvent<W
public TikTokWebsocketUnhandledMessageEvent(WebcastResponse.Message data) { public TikTokWebsocketUnhandledMessageEvent(WebcastResponse.Message data) {
super(data); super(data);
} }
public WebcastResponse.Message getMessage()
{
return this.getData();
}
} }

View File

@@ -28,15 +28,10 @@ import lombok.Getter;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
import java.awt.*; import java.awt.*;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream; import java.io.*;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.URL; import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
public class Picture { public class Picture {
@Getter @Getter
@@ -49,7 +44,6 @@ public class Picture {
} }
public static Picture map(io.github.jwdeveloper.tiktok.messages.data.Image profilePicture) { public static Picture map(io.github.jwdeveloper.tiktok.messages.data.Image profilePicture) {
var index = profilePicture.getUrlListCount() - 1; var index = profilePicture.getUrlListCount() - 1;
if (index < 0) { if (index < 0) {
return new Picture(""); return new Picture("");
@@ -74,12 +68,11 @@ public class Picture {
return CompletableFuture.supplyAsync(this::downloadImage); return CompletableFuture.supplyAsync(this::downloadImage);
} }
private BufferedImage download(String urlString) private BufferedImage download(String urlString) {
{ if (urlString.isEmpty()) {
if(urlString.isEmpty())
{
return null; return null;
} }
var baos = new ByteArrayOutputStream(); var baos = new ByteArrayOutputStream();
try (var is = new URL(urlString).openStream()) { try (var is = new URL(urlString).openStream()) {
var byteChunk = new byte[4096]; var byteChunk = new byte[4096];
@@ -103,4 +96,9 @@ public class Picture {
public static Picture Empty() { public static Picture Empty() {
return new Picture(""); 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() { public static Badge empty() {
return new Badge(); 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.data.models.Picture;
import io.github.jwdeveloper.tiktok.messages.data.BadgeStruct; import io.github.jwdeveloper.tiktok.messages.data.BadgeStruct;
import lombok.Getter;
@Getter
public class CombineBadge extends Badge { public class CombineBadge extends Badge {
private final Picture picture; private final Picture picture;
private final String text; private final String text;
private final String subText; private final String subText;
public CombineBadge(BadgeStruct.CombineBadge combineBadge) { public CombineBadge(BadgeStruct.CombineBadge combineBadge) {
picture = Picture.map(combineBadge.getIcon()); picture = Picture.map(combineBadge.getIcon());
text = combineBadge.getText().getDefaultPattern(); text = combineBadge.getText().getDefaultPattern();
subText = combineBadge.getStr(); 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.data.models.Picture;
import io.github.jwdeveloper.tiktok.messages.data.BadgeStruct; import io.github.jwdeveloper.tiktok.messages.data.BadgeStruct;
import lombok.Getter;
@Getter
public class PictureBadge extends Badge { public class PictureBadge extends Badge {
private final Picture picture; private final Picture picture;
public PictureBadge(BadgeStruct.ImageBadge imageBadge) {
public PictureBadge(BadgeStruct.ImageBadge imageBadge) {
picture = Picture.map(imageBadge.getImage()); 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; package io.github.jwdeveloper.tiktok.data.models.badges;
import io.github.jwdeveloper.tiktok.messages.data.BadgeStruct; import io.github.jwdeveloper.tiktok.messages.data.BadgeStruct;
import lombok.Getter;
@Getter
public class StringBadge extends Badge { public class StringBadge extends Badge {
private final String text;
public String text;
public StringBadge(BadgeStruct.StringBadge stringBadge) { public StringBadge(BadgeStruct.StringBadge stringBadge) {
this.text = stringBadge.getStr(); 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; package io.github.jwdeveloper.tiktok.data.models.badges;
import io.github.jwdeveloper.tiktok.messages.data.BadgeStruct; 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; private final String text;
public TextBadge(BadgeStruct.TextBadge textBadge) public TextBadge(BadgeStruct.TextBadge textBadge) {
{
this.text = textBadge.getDefaultPattern(); this.text = textBadge.getDefaultPattern();
} }
}
@Override
public String toString() {
return "TextBadge{text='" + text + "'}";
}
}

View File

@@ -1,3 +1,25 @@
/*
* 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.models.chest; package io.github.jwdeveloper.tiktok.data.models.chest;

View File

@@ -1,3 +1,25 @@
/*
* 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.
*/
// This enum is generated // This enum is generated
package io.github.jwdeveloper.tiktok.data.models.gifts; package io.github.jwdeveloper.tiktok.data.models.gifts;
@@ -19,6 +41,10 @@ public enum Gift {
LOVE_CHAT(6205, "Love Chat", 400, "https://storage.streamdps.com/iblock/440/4402267722e227b72adc97db92504b75/ae0f562146e701f32ae96761ac67c5cc.png"), 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"), 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"), DOUBLE_TROUBLE(8038, "Double trouble", 2988, "https://storage.streamdps.com/iblock/a23/a23f89b59cebf6d82ba64437e0ce52c9/d13464a899047febd2bd3db61835cb1b.webp"),
@@ -27,12 +53,16 @@ public enum Gift {
FALCON_6367(6367, "Falcon", 10999, "https://storage.streamdps.com/iblock/f88/f886e7678bef35f8c762a323386e6d23/7249e0af64c78d1d569a8d7a86ab58cd.png"), 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"), 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"), PANTHER_PAWS(7204, "Panther Paws", 199, "https://storage.streamdps.com/iblock/6e0/6e097d88e5e088d0228c702456e58450/72afb8bfa2231766da6817e911702d4b.webp"),
PANTHER_PAWS_8358(8358, "Panther Paws", 199, "https://storage.streamdps.com/iblock/a25/a25d2409e1d851566987913c9fb9860f/6aeb9164cf39e2602933d28dbd106119.webp"), PANTHER_PAWS_8358(8358, "Panther Paws", 199, "https://storage.streamdps.com/iblock/a25/a25d2409e1d851566987913c9fb9860f/6aeb9164cf39e2602933d28dbd106119.webp"),
GOLDEN_PARTY(9499, "Golden Party", 3000, "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/a00450f3e3aa1f01b62774950e5729c3.png~tplv-obj.jpg"),
SHOOTING_STARS(5753, "Shooting Stars", 1580, "https://storage.streamdps.com/iblock/b36/b36bb8c332ade25b2e591cd3ed164a99/a06c10f4dc562c24f4f5b6812b9fa01f.png"), SHOOTING_STARS(5753, "Shooting Stars", 1580, "https://storage.streamdps.com/iblock/b36/b36bb8c332ade25b2e591cd3ed164a99/a06c10f4dc562c24f4f5b6812b9fa01f.png"),
LION(6369, "Lion", 29999, "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/4fb89af2082a290b37d704e20f4fe729~tplv-obj.jpg"), LION(6369, "Lion", 29999, "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/4fb89af2082a290b37d704e20f4fe729~tplv-obj.jpg"),
@@ -47,6 +77,10 @@ public enum Gift {
MAKE_UP_BOX(6033, "Make-up Box", 1999, "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/a29aa87203ec09c699e3dafa1944b23e~tplv-obj.jpg"), MAKE_UP_BOX(6033, "Make-up Box", 1999, "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/a29aa87203ec09c699e3dafa1944b23e~tplv-obj.jpg"),
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"), 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"), RUBY_RED(8434, "Ruby red", 88, "https://storage.streamdps.com/iblock/405/405fcf52a1de3d14ab9834c1f30cc330/0deed9ee2c79ba6bf2005b0ce667bf60.webp"),
@@ -55,6 +89,8 @@ public enum Gift {
COTTON_CANDY(7265, "Cotton Candy", 700, "https://storage.streamdps.com/iblock/51f/51f64a93c515f4a45169f24a52179f2f/730beb9631b1af4edfaf714d7686df04.webp"), 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"), 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"), SUB_STAR(7072, "Sub Star", 1, "https://storage.streamdps.com/iblock/98f/98fea40fc19cc9dbd9a083b0844c163b/af7dd985812299d89f6cfa49c84e7eaf.webp"),
@@ -71,6 +107,8 @@ public enum Gift {
MIKE(7789, "Mike", 4000, "https://storage.streamdps.com/iblock/de0/de0da7b6ce6ba19125b1c4eb2fd2966a/6804a72c00714de05f9239be7bd5b515.webp"), 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(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"), HEADPHONE_8017(8017, "Headphone", 199, "https://storage.streamdps.com/iblock/055/05573a16af395b896b26847bc77fbb5e/55c0f27976902374940cfb54f22728d0.webp"),
@@ -79,6 +117,8 @@ public enum Gift {
ICE_CREAM_8963(8963, "Ice cream", 5, "https://storage.streamdps.com/iblock/f72/f726165be6e93bdc69724375e7931dde/2e749d8d397b3ce5e6bcc90402f27c7d.webp"), 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"), 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"), MARVELOUS_CONFETTI(7121, "Marvelous Confetti", 100, "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/fccc851d351716bc8b34ec65786c727d~tplv-obj.jpg"),
@@ -119,12 +159,16 @@ public enum Gift {
WATERMELON(8826, "Watermelon", 10, "https://storage.streamdps.com/iblock/84e/84e29ce96978961b12f1e88dd985b938/08e2a0ac2c2e2794aa2558e67d387639.webp"), 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"), 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"), 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"), 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"), 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"), CUBE(9184, "Cube", 10, "https://storage.streamdps.com/iblock/69d/69dab4e352882c0bd29c3864e24d80de/258857221189c76260b6af5eeb43e93b.webp"),
@@ -133,10 +177,16 @@ public enum Gift {
SPEEDBOAT(5763, "Speedboat", 1888, "https://storage.streamdps.com/iblock/55f/55f832ac0d4e25f2527b2cf87ae8af08/ec99908e1787ae32c1387a20db7ca5ac.png"), 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"), 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"), 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(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"), KISS_5481(5481, "Kiss", 150, "https://storage.streamdps.com/iblock/5cc/5cca201687ef878daf36dfe39fd26807/b2171e9cc191783679794f42246c4ceb.webp"),
@@ -155,6 +205,10 @@ public enum Gift {
COWBOY_HAT(8842, "Cowboy Hat", 199, "https://storage.streamdps.com/iblock/5f3/5f3df5eccbc82f458fdacd0f82d13e40/40980853c80e3da0e902a1db49ea9798.webp"), 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"), SPORTS_CAR(6089, "Sports Car", 7000, "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/e7ce188da898772f18aaffe49a7bd7db~tplv-obj.jpg"),
ICE_TEA(5464, "Ice Tea", 1, "https://storage.streamdps.com/iblock/531/5313a4ca89a7c7588a88898c8f1e9053/dab85392562772099474a050c251d340.png"), ICE_TEA(5464, "Ice Tea", 1, "https://storage.streamdps.com/iblock/531/5313a4ca89a7c7588a88898c8f1e9053/dab85392562772099474a050c251d340.png"),
@@ -163,6 +217,8 @@ public enum Gift {
CHOCO_PIE(6503, "Choco Pie", 10, "https://storage.streamdps.com/iblock/5a7/5a7610069bd417a2847f34c6c0b2821d/5faa955edd066d1140abb048f32be815.webp"), 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(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"), WHALE_DIVING_6820(6820, "Whale diving", 2150, "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/46fa70966d8e931497f5289060f9a794~tplv-obj.jpg"),
@@ -181,8 +237,12 @@ public enum Gift {
PEARL(5664, "Pearl", 800, "https://storage.streamdps.com/iblock/d42/d4241b9de546fb190964c12adeecabca/d03fe09dd3400422c55953555066487e.png"), 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"), 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"), 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"), FLORAL_BLOOM(5788, "Floral Bloom", 1500, "https://storage.streamdps.com/iblock/858/85827a8e5266c8d4c697d9aa930fead6/149392b39b041febde90bc4ea80ce1a5.png"),
@@ -199,14 +259,20 @@ public enum Gift {
GIFT_BOX_6835(6835, "Gift Box", 3999, "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/3646c259f8ce6f79c762ad00ce51dda0~tplv-obj.jpg"), GIFT_BOX_6835(6835, "Gift Box", 3999, "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/3646c259f8ce6f79c762ad00ce51dda0~tplv-obj.jpg"),
FISHING_GEAR(5956, "Fishing Gear", 199, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/1b2353958374f585e25b2f2344c6d0ad~tplv-obj.png"),
ARCADE_GAME(5876, "Arcade Game", 1200, "https://storage.streamdps.com/iblock/d5a/d5aaa3c8ef3d271c2f93709c3ff51e67/721d870d5a5d9d82d726ff5a9ba3aa5e.png"), ARCADE_GAME(5876, "Arcade Game", 1200, "https://storage.streamdps.com/iblock/d5a/d5aaa3c8ef3d271c2f93709c3ff51e67/721d870d5a5d9d82d726ff5a9ba3aa5e.png"),
ARCADE_GAME_7041(7041, "Arcade Game", 1200, "https://storage.streamdps.com/iblock/fd0/fd0785612b024900444a0a69083400ff/3181d6af50b05dd65a7ba75902bb5b94.webp"), 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"), 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"), 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"), 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"), SOCKS_AND_SANDALS(6618, "Socks and Sandals", 150, "https://storage.streamdps.com/iblock/da2/da28ef4030197f812686f10b2c3f06c7/7cb8ebff6f6028e2a56b2c0c268c3620.webp"),
@@ -217,6 +283,10 @@ public enum Gift {
GAMEPAD(6052, "Gamepad", 10, "https://storage.streamdps.com/iblock/711/711b578c104edcf1639ff4e2e7779660/6cbb6613fbbd40dac6dfd8857b05545a.png"), GAMEPAD(6052, "Gamepad", 10, "https://storage.streamdps.com/iblock/711/711b578c104edcf1639ff4e2e7779660/6cbb6613fbbd40dac6dfd8857b05545a.png"),
LIVE_FEST_CLAPPERS(9333, "LIVE Fest Clappers", 100, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/63e85e00169ec5be3bfa90bb004cda5e.png~tplv-obj.png"),
RAINBOW(8616, "Rainbow", 1, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/5fb7267489192fc77c4c8b647c124680~tplv-obj.png"),
ORANGE_JUICE(5778, "Orange Juice", 1, "https://storage.streamdps.com/iblock/3d6/3d635024d8744f8648306d56a5c4f62f/be0f5f006bd2350e904b23b607e4f06b.png"), ORANGE_JUICE(5778, "Orange Juice", 1, "https://storage.streamdps.com/iblock/3d6/3d635024d8744f8648306d56a5c4f62f/be0f5f006bd2350e904b23b607e4f06b.png"),
GERRY_THE_GIRAFFE(6842, "Gerry the Giraffe", 1000, "https://storage.streamdps.com/iblock/792/792ef3f53d86b5cb066d5c0bb5b00a87/91aa5cf7f51a533841bea8617419c54d.webp"), GERRY_THE_GIRAFFE(6842, "Gerry the Giraffe", 1000, "https://storage.streamdps.com/iblock/792/792ef3f53d86b5cb066d5c0bb5b00a87/91aa5cf7f51a533841bea8617419c54d.webp"),
@@ -231,6 +301,8 @@ public enum Gift {
TIKTOK_VOLCANO(6869, "TikTok Volcano", 4000, "https://storage.streamdps.com/iblock/e6d/e6d4c0d014c552ec6e8eccb804a7659f/9678f5e24b6e9b069b43c4f84a536d9f.webp"), 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"), _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"), ROMAN_EMPIRE(7166, "Roman Empire", 199, "https://storage.streamdps.com/iblock/c77/c778c4e5cd1c68a50dcc06e4bfc3aa08/48edf8b190d98b0a3cc4623e6cc9a22c.webp"),
@@ -251,6 +323,8 @@ public enum Gift {
PIZZA(7055, "Pizza", 40, "https://storage.streamdps.com/iblock/c9d/c9d7f483cc0059a1e8165bfbd1341688/307a559eb2b371b92b8ea36ae96bfa30.webp"), 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"), 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"), CLUB(6417, "Club", 2000, "https://storage.streamdps.com/iblock/49b/49be18ae5914346ffcaf15a519ba9c1c/41326cb23d22010f0c4a8edf5bd27615.webp"),
@@ -259,11 +333,15 @@ public enum Gift {
DANCING_CAPYBARAS(8806, "Dancing Capybaras", 2200, "https://storage.streamdps.com/iblock/ac2/ac2606f1dc2504c9a1b7974f40074c87/c243031480e8f2e4bbd8e7a43228ff1f.webp"), 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"), 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"), 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"), PINK_SHOES_8843(8843, "Pink shoes", 5, "https://storage.streamdps.com/iblock/e32/e328784531bfcd4773983c6a8e205a44/a3b5a6f4fa914fdf10b754ee59dc34a4.webp"),
@@ -279,8 +357,14 @@ public enum Gift {
SUNDAY_ROAST(6634, "Sunday Roast", 199, "https://storage.streamdps.com/iblock/218/218658dfe16bf8eeb11824cae5788028/95bbb526ea861ef2ba3dbe020431374f.webp"), 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"), 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"), 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"), TEAM_BRACELET(9139, "Team Bracelet", 2, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/54cb1eeca369e5bea1b97707ca05d189.png~tplv-obj.png"),
@@ -289,7 +373,9 @@ public enum Gift {
SAKURA_TRAIN(6244, "Sakura Train", 3999, "https://storage.streamdps.com/iblock/a8e/a8e50d5c5d0eaa42bd71dbeca3b1b95a/204910c857958e7e9efd0178d30a2fbe.png"), SAKURA_TRAIN(6244, "Sakura Train", 3999, "https://storage.streamdps.com/iblock/a8e/a8e50d5c5d0eaa42bd71dbeca3b1b95a/204910c857958e7e9efd0178d30a2fbe.png"),
AUTUMN_LEAVES(6967, "Autumn Leaves", 500, "https://storage.streamdps.com/iblock/f04/f042339687e8abaa2fc0e1976d9b11f4/251a0624bc3a23ba39d75467868dcbf8.webp"), AUTUMN_LEAVES(5890, "Autumn leaves", 500, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/30adcaf443df63e3bfd2751ad251f87d~tplv-obj.png"),
FRANGIPANI(5992, "Frangipani", 1, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/7464fad59650123fe0989e426618847d~tplv-obj.png"),
SKI_GOGGLES(7781, "Ski Goggles", 199, "https://storage.streamdps.com/iblock/f42/f42cbce436db4e60adbf85641a768a12/fa9a4cea3c23829cf6f0725fea8d3c1a.webp"), SKI_GOGGLES(7781, "Ski Goggles", 199, "https://storage.streamdps.com/iblock/f42/f42cbce436db4e60adbf85641a768a12/fa9a4cea3c23829cf6f0725fea8d3c1a.webp"),
@@ -297,12 +383,16 @@ public enum Gift {
BIGFOOT(9147, "Bigfoot", 3000, "https://storage.streamdps.com/iblock/f95/f95a4fcfa57150610fa50542db5b0990/ecb879cd751e580d3fe92770788c1735.webp"), 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"), 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"), 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"), 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(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"), TIKTOK_UNIVERSE_6039(6039, "TikTok Universe", 34999, "https://storage.streamdps.com/iblock/49d/49d934dc15cf5efc3ebef902a5974d56/04799e79cb4bd04a20d77d2f3fa9922d.png"),
@@ -317,6 +407,8 @@ public enum Gift {
PINCH_CHEEK(6694, "Pinch Cheek", 199, "https://storage.streamdps.com/iblock/f6c/f6c95968ca266cbb2527af09989eaea0/27be132509198253b5c48e5495038e5b.png"), 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"), 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"), ELEPHANT_TRUNK(8260, "Elephant trunk", 299, "https://storage.streamdps.com/iblock/1ea/1eafea22e99969312cda7c142d8eb3c5/59f72e0dce1bc4fcf83a34f56872b492.webp"),
@@ -329,8 +421,12 @@ public enum Gift {
RABBIT(6348, "Rabbit", 1999, "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/61b42d630091b661e82fc8ed400b1de2~tplv-obj.jpg"), RABBIT(6348, "Rabbit", 1999, "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/61b42d630091b661e82fc8ed400b1de2~tplv-obj.jpg"),
ASMR(6240, "ASMR", 10, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/748e74c8309e08dbc5b03e03f28a0ea0~tplv-obj.png"),
ROSE(5655, "Rose", 1, "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/eba3a9bb85c33e017f3648eaf88d7189~tplv-obj.jpg"), 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"), 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"), ROSA(7997, "Rosa", 10, "https://storage.streamdps.com/iblock/486/486a2490c987c2bb97b6068fd5aac5ab/49d9045fcfe94bbfbd08c3363bb4512a.webp"),
@@ -347,10 +443,16 @@ public enum Gift {
COTTON_S_SHELL(8352, "Cotton's Shell", 5, "https://storage.streamdps.com/iblock/766/7665d59f0ef96aecd2dac6fc5b0c19a4/3b169a12b4f8686c68d596f6d47d2f77.webp"), 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"), 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"), 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"), 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"), PANDA_SKYDIVING(8812, "Panda skydiving", 2000, "https://storage.streamdps.com/iblock/a29/a29903a975ce45f7b9939b510412fcee/051afc0510a7349a9ebfcde9e0fdec24.webp"),
@@ -361,10 +463,16 @@ public enum Gift {
GAMER_CYBER_MASK(7895, "Gamer Cyber Mask", 399, "https://storage.streamdps.com/iblock/383/383652cc1fd3cae9402eeae3a8f5ee1e/df8a16397bb0ed28c0e522b4cfb26500.webp"), 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"), KO(7542, "KO", 20, "https://storage.streamdps.com/iblock/e5e/e5efb63a21695a08d9647508aca3c95e/cffda8af4cc1a9f4a66eb01b11f4db85.webp"),
KO_7655(7655, "KO", 20, "https://storage.streamdps.com/iblock/aa6/aa613e765fe5c42519bd83d2d4705118/7db90e1f83b8c87c74dfdc8ee88440cb.webp"), KO_7655(7655, "KO", 20, "https://storage.streamdps.com/iblock/aa6/aa613e765fe5c42519bd83d2d4705118/7db90e1f83b8c87c74dfdc8ee88440cb.webp"),
MAGIC_FOREST(9135, "Magic Forest", 6000, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/63a758dbef9788f690e97cd65dbbb8d2~tplv-obj.png"),
LEON_AND_LILI(8916, "Leon and Lili", 9699, "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/6958244f3eeb69ce754f735b5833a4aa.png~tplv-obj.jpg"), LEON_AND_LILI(8916, "Leon and Lili", 9699, "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/6958244f3eeb69ce754f735b5833a4aa.png~tplv-obj.jpg"),
MAGIC_STAGE(6263, "Magic Stage", 2599, "https://storage.streamdps.com/iblock/399/399df717aefef9de9259e8256221076f/dfa2835c35b2177701ee65139bdfc59a.png"), MAGIC_STAGE(6263, "Magic Stage", 2599, "https://storage.streamdps.com/iblock/399/399df717aefef9de9259e8256221076f/dfa2835c35b2177701ee65139bdfc59a.png"),
@@ -373,6 +481,8 @@ public enum Gift {
DON_T_DO_IT(7688, "Dont Do It", 500, "https://storage.streamdps.com/iblock/dca/dcac97e4190d46d113f4bdf2918ee173/4fae166b3f3273b9dbbc2a86bea0ec18.webp"), 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"), 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"), HANDS_UP(8244, "Hands Up", 499, "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/f4d906542408e6c87cf0a42f7426f0c6~tplv-obj.jpg"),
@@ -383,6 +493,8 @@ public enum Gift {
PAIMON_SURPRISE(8299, "Paimon Surprise", 1299, "https://storage.streamdps.com/iblock/ffc/ffc784ca54363f5d1d0c195419a3c19b/27096967caade6f066ce748bf5327244.webp"), PAIMON_SURPRISE(8299, "Paimon Surprise", 1299, "https://storage.streamdps.com/iblock/ffc/ffc784ca54363f5d1d0c195419a3c19b/27096967caade6f066ce748bf5327244.webp"),
GOOD_EVENING(8267, "Good Evening", 399, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/0015a756ff783f37a2cf3b5d634b3cd6~tplv-obj.png"),
HI_MAY(6471, "Hi May", 88, "https://storage.streamdps.com/iblock/970/970b0a868ce24c4b7b7059a904fa7b00/622d597d3cec282d6d2c8129fedd5075.png"), HI_MAY(6471, "Hi May", 88, "https://storage.streamdps.com/iblock/970/970b0a868ce24c4b7b7059a904fa7b00/622d597d3cec282d6d2c8129fedd5075.png"),
WEDDING(6286, "Wedding", 1400, "https://storage.streamdps.com/iblock/7be/7beeb7f1098cf5f784739a0be38a06f8/0678483823c912e4dea96fa19a2f0d86.png"), WEDDING(6286, "Wedding", 1400, "https://storage.streamdps.com/iblock/7be/7beeb7f1098cf5f784739a0be38a06f8/0678483823c912e4dea96fa19a2f0d86.png"),
@@ -405,6 +517,8 @@ public enum Gift {
MUSIC_NOTE(5915, "Music Note", 169, "https://storage.streamdps.com/iblock/cc1/cc17f136f458a86943d7fd503c0a34b4/c56797b8b830d159f31fe5ca5527f586.png"), MUSIC_NOTE(5915, "Music Note", 169, "https://storage.streamdps.com/iblock/cc1/cc17f136f458a86943d7fd503c0a34b4/c56797b8b830d159f31fe5ca5527f586.png"),
FLOWER_SHOW(5831, "Flower Show", 500, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/b6266323ef3ea0d313cbab6911ff8c46~tplv-obj.png"),
MAXWELL(8189, "Maxwell", 10, "https://storage.streamdps.com/iblock/82b/82b7041dcdd8fcc1842c0dd7b5a63099/73736d5ec979ad00f4b771397d9b998b.webp"), MAXWELL(8189, "Maxwell", 10, "https://storage.streamdps.com/iblock/82b/82b7041dcdd8fcc1842c0dd7b5a63099/73736d5ec979ad00f4b771397d9b998b.webp"),
LOVE_DROP(8277, "Love Drop", 1800, "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/1ea684b3104abb725491a509022f7c02~tplv-obj.jpg"), LOVE_DROP(8277, "Love Drop", 1800, "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/1ea684b3104abb725491a509022f7c02~tplv-obj.jpg"),
@@ -413,6 +527,8 @@ public enum Gift {
YACHT(6103, "Yacht", 9888, "https://storage.streamdps.com/iblock/b6c/b6c9d3c6df6733cc85149897764d2c6b/023d358a3d7a7a330ed006eb07117582.png"), YACHT(6103, "Yacht", 9888, "https://storage.streamdps.com/iblock/b6c/b6c9d3c6df6733cc85149897764d2c6b/023d358a3d7a7a330ed006eb07117582.png"),
YACHT_9501(9501, "Yacht", 20000, "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/e69e2626f6ff43d1c1f2b8ae5ea42514.png~tplv-obj.jpg"),
WINDOW_BASKET(8648, "Window basket", 500, "https://storage.streamdps.com/iblock/a8d/a8d0c44c86385d4cd02ad2d840dcb148/8bbdca8666946a2e7172b3eaeed02303.webp"), WINDOW_BASKET(8648, "Window basket", 500, "https://storage.streamdps.com/iblock/a8d/a8d0c44c86385d4cd02ad2d840dcb148/8bbdca8666946a2e7172b3eaeed02303.webp"),
SWAN(5897, "Swan", 699, "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/97a26919dbf6afe262c97e22a83f4bf1~tplv-obj.jpg"), SWAN(5897, "Swan", 699, "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/97a26919dbf6afe262c97e22a83f4bf1~tplv-obj.jpg"),
@@ -425,8 +541,12 @@ public enum Gift {
FOOTBALL_HELMET(7851, "Football Helmet", 50, "https://storage.streamdps.com/iblock/9cc/9cce61670c1a81b7954fcf3520dc15a2/b78182e9fd2ff1c6ae1256abd8e2e2bf.webp"), 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"), 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"),
GAME_CONTROLLER(6467, "Game Controller", 100, "https://storage.streamdps.com/iblock/603/6032c1b0d5c2c07abe04956b3cdd45cd/d75d75a7e81f96f39d2ffd574063924f.png"), GAME_CONTROLLER(6467, "Game Controller", 100, "https://storage.streamdps.com/iblock/603/6032c1b0d5c2c07abe04956b3cdd45cd/d75d75a7e81f96f39d2ffd574063924f.png"),
GAME_CONTROLLER_6960(6960, "Game Controller", 100, "https://storage.streamdps.com/iblock/030/030f63329d68d21c5faacab88006a17f/fbb8dd78b47184321d93e3ae5a1f2cca.webp"), GAME_CONTROLLER_6960(6960, "Game Controller", 100, "https://storage.streamdps.com/iblock/030/030f63329d68d21c5faacab88006a17f/fbb8dd78b47184321d93e3ae5a1f2cca.webp"),
@@ -435,6 +555,12 @@ public enum Gift {
HANGING_LIGHTS(5937, "Hanging Lights", 199, "https://storage.streamdps.com/iblock/e03/e03da22fa8c302dbf1d9439c65380549/6d9f912b5a9253f91c01ed58e3ccbe47.png"), 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"), 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"), DANCING_QUEENS(9240, "Dancing queens", 20000, "https://storage.streamdps.com/iblock/c79/c793af446369ecef5238e73312c84ccd/464a76f3e6eaee9afc771f45a4bba9df.webp"),
@@ -481,20 +607,30 @@ public enum Gift {
FOUNTAIN(8887, "Fountain", 1200, "https://storage.streamdps.com/iblock/07d/07d678346c7eb588bc3cbddf343ab791/8f8f50f5350e4b1c0b151aff333e43a4.webp"), FOUNTAIN(8887, "Fountain", 1200, "https://storage.streamdps.com/iblock/07d/07d678346c7eb588bc3cbddf343ab791/8f8f50f5350e4b1c0b151aff333e43a4.webp"),
KOALA(5822, "Koala", 10, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/22c8fa54da366c111f7bb915d4429e2d~tplv-obj.png"),
GARLAND_HEADPIECE(6437, "Garland Headpiece", 199, "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/bdbdd8aeb2b69c173a3ef666e63310f3~tplv-obj.jpg"), GARLAND_HEADPIECE(6437, "Garland Headpiece", 199, "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/bdbdd8aeb2b69c173a3ef666e63310f3~tplv-obj.jpg"),
AEROBIC_HEADBAND(9255, "Aerobic headband", 99, "https://storage.streamdps.com/iblock/3d9/3d98c2fbc96922da37a9d22881bb06b9/0a99af132ab8e3fe9806d2412abc6bf0.webp"), AEROBIC_HEADBAND(9255, "Aerobic headband", 99, "https://storage.streamdps.com/iblock/3d9/3d98c2fbc96922da37a9d22881bb06b9/0a99af132ab8e3fe9806d2412abc6bf0.webp"),
MOUNTAINS(9516, "Mountains", 12000, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/51a7d74bcb4a6417be59f0ffc0b77e96.png~tplv-obj.png"),
FULL_MOON(7222, "Full moon", 299, "https://storage.streamdps.com/iblock/e64/e64dd135280596ce7f1aebbdc3e33a80/494b818b6a4217f1807255ca148c7b2d.webp"), FULL_MOON(7222, "Full moon", 299, "https://storage.streamdps.com/iblock/e64/e64dd135280596ce7f1aebbdc3e33a80/494b818b6a4217f1807255ca148c7b2d.webp"),
CHEEMS_DOG(6486, "Cheems Dog", 199, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/d2c9e50efa3b9ff1ed31c96440a9d3a1~tplv-obj.png"),
CAT_PAWS(6046, "Cat Paws", 1, "https://storage.streamdps.com/iblock/c04/c04061e18b637df6759417bfe5418c9c/89bc2c5278f4a3c28acebdd10f6bc089.webp"), CAT_PAWS(6046, "Cat Paws", 1, "https://storage.streamdps.com/iblock/c04/c04061e18b637df6759417bfe5418c9c/89bc2c5278f4a3c28acebdd10f6bc089.webp"),
WAVING_HAND(5959, "Waving Hand", 7, "https://storage.streamdps.com/iblock/6da/6da44060164719c3bcb171fb06d6d0d4/a80d1fa6879b0970246f41c444dca47c.webp"), WAVING_HAND(5959, "Waving Hand", 7, "https://storage.streamdps.com/iblock/6da/6da44060164719c3bcb171fb06d6d0d4/a80d1fa6879b0970246f41c444dca47c.webp"),
FRUITS_HAT_(6744, "Fruits Hat ", 199, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/2316b31fc5259cc29f281d88fbca0568~tplv-obj.png"),
BUBBLES(5850, "Bubbles", 500, "https://storage.streamdps.com/iblock/4b1/4b1a012395fd18f6ed835539089dd3c3/98688050698f0180bdd46018a4e98ec1.png"), BUBBLES(5850, "Bubbles", 500, "https://storage.streamdps.com/iblock/4b1/4b1a012395fd18f6ed835539089dd3c3/98688050698f0180bdd46018a4e98ec1.png"),
SILVER_SPORTS_CAR(8433, "Silver sports car", 5000, "https://storage.streamdps.com/iblock/132/132eb0981780e3e268f844106037b277/a1afff85fc6c53482fccbea21709d36b.webp"), SILVER_SPORTS_CAR(8433, "Silver sports car", 5000, "https://storage.streamdps.com/iblock/132/132eb0981780e3e268f844106037b277/a1afff85fc6c53482fccbea21709d36b.webp"),
LIGHTNING_STORM(9515, "Lightning Storm", 6000, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/6f673fbb0ae6860e2b1e254538c958ba.png~tplv-obj.png"),
RACCOON(8448, "Raccoon", 15, "https://storage.streamdps.com/iblock/539/5396582d174489f32525f871cb3087f8/041896a3554f3d4b8c86f486bc81b125.webp"), RACCOON(8448, "Raccoon", 15, "https://storage.streamdps.com/iblock/539/5396582d174489f32525f871cb3087f8/041896a3554f3d4b8c86f486bc81b125.webp"),
HEART_ME(7934, "Heart Me", 1, "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/d56945782445b0b8c8658ed44f894c7b~tplv-obj.jpg"), HEART_ME(7934, "Heart Me", 1, "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/d56945782445b0b8c8658ed44f894c7b~tplv-obj.jpg"),
@@ -503,6 +639,8 @@ public enum Gift {
TIKTOK_TROPHY(7357, "TikTok Trophy", 699, "https://storage.streamdps.com/iblock/7f6/7f6d5df92bf4b5b559567b9a870d485f/1811197db0860ff395435d51d35598ef.webp"), TIKTOK_TROPHY(7357, "TikTok Trophy", 699, "https://storage.streamdps.com/iblock/7f6/7f6d5df92bf4b5b559567b9a870d485f/1811197db0860ff395435d51d35598ef.webp"),
LOVE_BOMB(6050, "Love Bomb", 299, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/2a1c1b14f5e9f7be5d76fa4928f574f1~tplv-obj.png"),
TENNIS(6169, "Tennis", 1, "https://storage.streamdps.com/iblock/f20/f20121609887f7ff35952c1bc52529e2/9ff66229b1f81d21b15444ba2b53db98.png"), TENNIS(6169, "Tennis", 1, "https://storage.streamdps.com/iblock/f20/f20121609887f7ff35952c1bc52529e2/9ff66229b1f81d21b15444ba2b53db98.png"),
NECKLACE(5662, "Necklace", 400, "https://storage.streamdps.com/iblock/a40/a40013bbd1e38e11c0772f8b605c6c25/567d58bd02385de4af1523980cb03a85.png"), NECKLACE(5662, "Necklace", 400, "https://storage.streamdps.com/iblock/a40/a40013bbd1e38e11c0772f8b605c6c25/567d58bd02385de4af1523980cb03a85.png"),
@@ -515,10 +653,16 @@ public enum Gift {
BIRTHDAY_CAKE_9097(9097, "Birthday Cake", 1, "https://storage.streamdps.com/iblock/5b9/5b9eca4a99e965cb25183681a07a5276/c28f7e9c4a8e42460225ff2d12300ae7.webp"), 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"), 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"), 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"), 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"), SHINY_AIR_BALLOON(7123, "Shiny air balloon", 1000, "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/9e7ebdca64b8f90fcc284bb04ab92d24~tplv-obj.jpg"),
@@ -531,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"), 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"), 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(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"), 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"), 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"), HAPPY_PARTY(8247, "Happy Party", 6999, "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/41774a8ba83c59055e5f2946d51215b4~tplv-obj.jpg"),
@@ -561,8 +711,12 @@ public enum Gift {
HI_JULY(6603, "Hi July", 1, "https://storage.streamdps.com/iblock/e03/e0301a9670584be92d945ff3cb889b99/0fbb7b11f916953201588b5bfbcb3f5a.png"), 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"), 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"), 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"), MAKE_IT_RAIN(5336, "Make it rain", 500, "https://storage.streamdps.com/iblock/770/770e03c64144e6d7830e884cd7140a8a/47af803e978121e760d649d47e67de50.png"),
@@ -585,16 +739,26 @@ public enum Gift {
SWING(5899, "Swing", 399, "https://storage.streamdps.com/iblock/8a1/8a16a7c5d463793c8c3ab5aa407a87d8/dee86ec9c8e98ebcc58e2e3c09b93d10.png"), SWING(5899, "Swing", 399, "https://storage.streamdps.com/iblock/8a1/8a16a7c5d463793c8c3ab5aa407a87d8/dee86ec9c8e98ebcc58e2e3c09b93d10.png"),
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"), 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"), 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"), 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"),
FLYING_JETS(7482, "Flying Jets", 5000, "https://storage.streamdps.com/iblock/5a4/5a4f3c7adc31f60326e3adf1a3a20bf9/bc96de02ceba4b91c1f9c996293974b4.webp"), FLYING_JETS(7482, "Flying Jets", 5000, "https://storage.streamdps.com/iblock/5a4/5a4f3c7adc31f60326e3adf1a3a20bf9/bc96de02ceba4b91c1f9c996293974b4.webp"),
FLYING_JETS_7720(7720, "Flying Jets", 5000, "https://storage.streamdps.com/iblock/738/73887ee5dc4a63709a10a2e3eff67b7c/1588215b603e2495582288471573cd57.webp"), FLYING_JETS_7720(7720, "Flying Jets", 5000, "https://storage.streamdps.com/iblock/738/73887ee5dc4a63709a10a2e3eff67b7c/1588215b603e2495582288471573cd57.webp"),
FLYING_JETS_9500(9500, "Flying Jets", 5000, "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/1d067d13988e8754ed6adbebd89b9ee8.png~tplv-obj.jpg"),
INDOOR_FAN(6565, "Indoor Fan", 199, "https://storage.streamdps.com/iblock/499/499dc6bf36be95e90398a56d18bfeebe/231f634c0c86d034f193477f208f66ca.webp"), INDOOR_FAN(6565, "Indoor Fan", 199, "https://storage.streamdps.com/iblock/499/499dc6bf36be95e90398a56d18bfeebe/231f634c0c86d034f193477f208f66ca.webp"),
MIC(5650, "Mic", 5, "https://storage.streamdps.com/iblock/1db/1dbec91a90cdeca9f7fb1ea7280ad5cd/cae0a287f4d2e8d0e1558dcbb4aa3b2f.png"), MIC(5650, "Mic", 5, "https://storage.streamdps.com/iblock/1db/1dbec91a90cdeca9f7fb1ea7280ad5cd/cae0a287f4d2e8d0e1558dcbb4aa3b2f.png"),
@@ -603,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"), 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"), 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"), 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"), 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"), 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"), DJ_ALIEN(8988, "DJ Alien", 5000, "https://storage.streamdps.com/iblock/67c/67cd7b9372f25b4f3558eacdfb83dc8b/059b6bf7b8c268d525fd9295fac0eb61.webp"),
@@ -621,14 +789,22 @@ public enum Gift {
ROMANTIC_CARRIAGE(5627, "Romantic Carriage", 6000, "https://storage.streamdps.com/iblock/681/68132980826d9ddb208928c54a798f7f/e4f143cb38a0687729539972b2132ac1.png"), ROMANTIC_CARRIAGE(5627, "Romantic Carriage", 6000, "https://storage.streamdps.com/iblock/681/68132980826d9ddb208928c54a798f7f/e4f143cb38a0687729539972b2132ac1.png"),
AMAZING(5983, "Amazing", 5, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/18256fd3f4402601dd07c83adae3e9a2~tplv-obj.png"),
BRIDAL_VEIL(5902, "Bridal Veil", 299, "https://storage.streamdps.com/iblock/ac0/ac0cbd1870dd92251f6ef620acb652e5/fe8eca664be736231b8e8e2cc2237a15.png"), BRIDAL_VEIL(5902, "Bridal Veil", 299, "https://storage.streamdps.com/iblock/ac0/ac0cbd1870dd92251f6ef620acb652e5/fe8eca664be736231b8e8e2cc2237a15.png"),
TULIP_BOX(5325, "Tulip Box", 200, "https://storage.streamdps.com/iblock/d44/d4471e5deb9cb5831f846ca4c9df9c5d/7d1236ecd67b3e655c3dfd72673a423d.png"), 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"), 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"), 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"), 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"), MARCH(7976, "March", 1, "https://storage.streamdps.com/iblock/ba4/ba44cb084cab8c9c63b4513a145813f4/56531d239586a3d4552859cb2b23314d.webp"),
@@ -641,12 +817,16 @@ public enum Gift {
STAR(6432, "Star", 99, "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/485175fda92f4d2f862e915cbcf8f5c4~tplv-obj.jpg"), 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"), 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"), 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"), 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"), DAISIES(6447, "Daisies", 1, "https://storage.streamdps.com/iblock/e11/e110e47562d77ab5fa26cc31e840f801/a4a1823ef2c1bc65c4dc2a4e82ec446b.png"),
@@ -655,6 +835,12 @@ public enum Gift {
CAMPING(6322, "Camping", 250, "https://storage.streamdps.com/iblock/9a9/9a9370a392311149be37e7c40c3e960d/ecb9dcdacf3a2ae0abef79baf0c4f41c.webp"), 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"), 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"), TICKET(6856, "Ticket", 10, "https://storage.streamdps.com/iblock/434/434746bffe494ac6ad2eb5e7e4384955/92e426ea0b4d4a9f89d7e2786115cd20.webp"),
@@ -669,8 +855,16 @@ public enum Gift {
CHOCOLATE(5860, "Chocolate", 1, "https://storage.streamdps.com/iblock/522/52287f41673e2fd836c83ec78e95f08a/77307666e41e09e54052fd321c2906c4.png"), CHOCOLATE(5860, "Chocolate", 1, "https://storage.streamdps.com/iblock/522/52287f41673e2fd836c83ec78e95f08a/77307666e41e09e54052fd321c2906c4.png"),
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"), 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"), 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"), POOL_PARTY(5938, "Pool Party", 4999, "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/4147c5bcfad9623c693f83d5d6cba1f7~tplv-obj.jpg"),
@@ -709,6 +903,8 @@ public enum Gift {
SPACESHIP(6588, "Spaceship", 13999, "https://storage.streamdps.com/iblock/fb1/fb1096568dcc97c2575dec7441d0d651/245c4c7ce9bf5d5378586eb3a2478b42.webp"), 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"), 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"), LION_S_MANE(7985, "Lion's Mane", 500, "https://storage.streamdps.com/iblock/267/2670a5a8c9666b7afffb3255c2c104ee/abe9a0e7a6ef8b83d94df90f3a356748.webp"),
@@ -735,8 +931,12 @@ public enum Gift {
NEW_UNIVERSE(9081, "New Universe", 1, "https://storage.streamdps.com/iblock/ff9/ff906a964a6ad9c4504438302d9354b8/3ee4796c239930c395afb3d7ef10295a.webp"), 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"), 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"), 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"), EMAIL_MESSAGE(6199, "Email Message", 1000, "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/c959df6dbffd6f07849d22d2c3c07861~tplv-obj.jpg"),
@@ -747,8 +947,16 @@ public enum Gift {
HI_MARCH(7977, "Hi March", 88, "https://storage.streamdps.com/iblock/e22/e2266686271c7a90ff04517f248c6f73/0459d679c01a5bfa5a4be1d61ec81ec8.webp"), 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"), 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"), I_LOVE_TR(7139, "I LOVE TR", 1, "https://storage.streamdps.com/iblock/84d/84d68e92c471e7da792aa98d856c824c/7728ac60043efb9c96e2ce0f77dbef31.webp"),
SHIBA_INU(5482, "Shiba Inu", 222, "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/ddbcee02f5b86b803b0ec34357cd82ec.png~tplv-obj.jpg"), SHIBA_INU(5482, "Shiba Inu", 222, "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/ddbcee02f5b86b803b0ec34357cd82ec.png~tplv-obj.jpg"),
@@ -779,10 +987,18 @@ public enum Gift {
RHYTHMIC_BEAR(9468, "Rhythmic Bear", 2999, "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/16eacf541e4bd6816e88139d079519f5.png~tplv-obj.jpg"), RHYTHMIC_BEAR(9468, "Rhythmic Bear", 2999, "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/resource/16eacf541e4bd6816e88139d079519f5.png~tplv-obj.jpg"),
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"), 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"), 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"), 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"), GO_SNACKING(7021, "Go Snacking", 1, "https://storage.streamdps.com/iblock/666/6661d244aca6ec5f3de19372316e871e/f967ba18a333cd1489396cb608371824.webp"),
@@ -793,12 +1009,16 @@ public enum Gift {
CHILL(6704, "Chill", 5, "https://storage.streamdps.com/iblock/7df/7dfcee6b2702691bf9c8ca0966b3c4b1/144aa8fff9ce8c64aa7fcb507bf6c1cd.webp"), 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(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"), TAKE_A_DRIVE_7631(7631, "Take a Drive", 1200, "https://storage.streamdps.com/iblock/c5b/c5b1ae3782864918bcb70d9e92046b87/8f3b4f952004f1aaef4bccfd69b19568.webp"),
FRUITS_HAT(7091, "Fruits Hat", 199, "https://storage.streamdps.com/iblock/404/404cc4794702cc6feb93bf4517bc0762/05846cb2d9548cf2f0573159110ecb64.webp"), FRUITS_HAT(7091, "Fruits Hat", 199, "https://storage.streamdps.com/iblock/404/404cc4794702cc6feb93bf4517bc0762/05846cb2d9548cf2f0573159110ecb64.webp"),
SPINNING_TOP(6483, "Spinning Top", 10, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/6cde70e04a6b40a9879f7b99ff191808~tplv-obj.png"),
MISHKA_BEAR(5486, "Mishka Bear", 100, "https://storage.streamdps.com/iblock/880/8809f52dbf40e0d670067f8c223d7c04/c603798bc6cd2bdc5a032ddbeb55e258.png"), MISHKA_BEAR(5486, "Mishka Bear", 100, "https://storage.streamdps.com/iblock/880/8809f52dbf40e0d670067f8c223d7c04/c603798bc6cd2bdc5a032ddbeb55e258.png"),
MISHKA_BEAR_5566(5566, "Mishka Bear", 100, "https://storage.streamdps.com/iblock/010/010ccc7a5d5e21231b46cea3223d5b1f/aa9c15ca87e4df8dad9be22164978fc2.png"), MISHKA_BEAR_5566(5566, "Mishka Bear", 100, "https://storage.streamdps.com/iblock/010/010ccc7a5d5e21231b46cea3223d5b1f/aa9c15ca87e4df8dad9be22164978fc2.png"),
@@ -809,6 +1029,8 @@ public enum Gift {
BALALAIKA(5927, "Balalaika", 100, "https://storage.streamdps.com/iblock/d88/d88bc38371769262c006dccbaa43c9ff/1e0f5adda0546879e07126492ba6001c.webp"), 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"), 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"), SPEEDSTER(8418, "Speedster", 15000, "https://storage.streamdps.com/iblock/96a/96a5a249a1701c3c03e0b2427bad3b2f/63fb5582c89c17f275fc99505505b719.webp"),
@@ -827,8 +1049,12 @@ public enum Gift {
BIG_LOVE(7224, "Big Love", 5, "https://storage.streamdps.com/iblock/9d7/9d791fea266e119ffd938095526a1b55/1923108683e8c0aba3b78e1d0e8137cf.webp"), BIG_LOVE(7224, "Big Love", 5, "https://storage.streamdps.com/iblock/9d7/9d791fea266e119ffd938095526a1b55/1923108683e8c0aba3b78e1d0e8137cf.webp"),
TGIF(6592, "TGIF", 1, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/2734231d880b5cd20149f4cc8c760279~tplv-obj.png"),
GOLDEN_GAMEPAD(6582, "Golden Gamepad", 30, "https://storage.streamdps.com/iblock/e85/e85940610dd45adc8733b51106c60712/ca839e1139ca0b94070c1e38093e95ec.png"), GOLDEN_GAMEPAD(6582, "Golden Gamepad", 30, "https://storage.streamdps.com/iblock/e85/e85940610dd45adc8733b51106c60712/ca839e1139ca0b94070c1e38093e95ec.png"),
COCONUT_TREE(5794, "Coconut Tree", 199, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/eb0923dbab5251f4c2e0496b11b55c4f~tplv-obj.png"),
SUMMER_BAND(6555, "Summer Band", 3999, "https://storage.streamdps.com/iblock/43b/43b88814d979720d80a6e17258ab3bd8/b1abf3d90ae212317d6ae339ed5f5be7.png"), SUMMER_BAND(6555, "Summer Band", 3999, "https://storage.streamdps.com/iblock/43b/43b88814d979720d80a6e17258ab3bd8/b1abf3d90ae212317d6ae339ed5f5be7.png"),
GORILLA(8602, "Gorilla", 30000, "https://storage.streamdps.com/iblock/1e2/1e29b9d1a0263f1487498dc556cdcbc1/bec227242f8c9b258855071aa050ac17.webp"), GORILLA(8602, "Gorilla", 30000, "https://storage.streamdps.com/iblock/1e2/1e29b9d1a0263f1487498dc556cdcbc1/bec227242f8c9b258855071aa050ac17.webp"),
@@ -851,6 +1077,8 @@ public enum Gift {
TEASING(6390, "Teasing", 401, "https://storage.streamdps.com/iblock/e14/e14c9b35975f1da5b8a5e3f116dae2bb/9f8cea9b65620e8376e44802c25ddf27.png"), TEASING(6390, "Teasing", 401, "https://storage.streamdps.com/iblock/e14/e14c9b35975f1da5b8a5e3f116dae2bb/9f8cea9b65620e8376e44802c25ddf27.png"),
CRICKET(6006, "Cricket", 99, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/408d55c0526ada808be7db3e22c02a56~tplv-obj.png"),
FOAMY_DRINK(6740, "Foamy Drink", 100, "https://storage.streamdps.com/iblock/cc8/cc8133c73d5ca2cb5fde306f5b4e2a11/fb273956755fe6fbf7263023a9c36ebe.webp"), FOAMY_DRINK(6740, "Foamy Drink", 100, "https://storage.streamdps.com/iblock/cc8/cc8133c73d5ca2cb5fde306f5b4e2a11/fb273956755fe6fbf7263023a9c36ebe.webp"),
CAPYBARA(8217, "Capybara", 30, "https://storage.streamdps.com/iblock/e94/e944534be54186446d7c38563c772029/553d899c4bd4be31e7b051bb36e842f8.webp"), CAPYBARA(8217, "Capybara", 30, "https://storage.streamdps.com/iblock/e94/e944534be54186446d7c38563c772029/553d899c4bd4be31e7b051bb36e842f8.webp"),
@@ -863,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"), 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"), 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(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"), 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"), SUITCASE(8597, "Suitcase", 199, "https://storage.streamdps.com/iblock/50f/50f04937063753d6de255d2b5a080c1c/4f101c7c50ddbe8bd26a2ce5f8c16896.webp"),
@@ -881,6 +1113,8 @@ public enum Gift {
DAFFODILS(6435, "Daffodils", 99, "https://storage.streamdps.com/iblock/2ed/2edf26fa90a7b3ca44c0d7c77a765c77/c333c68579488e9a36f4130481932b7c.png"), 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(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"), TINY_DINY_7591(7591, "Tiny Diny", 10, "https://storage.streamdps.com/iblock/b24/b24309d4ea6722875678e492ae12fb3f/864ac7928a78b43be2d1ee93915a53f5.webp"),
@@ -893,10 +1127,18 @@ public enum Gift {
TTEOKBOKKI(5645, "Tteokbokki", 5, "https://storage.streamdps.com/iblock/81c/81ca5954462f21f506095fe410dd2aaf/c07e9b0bb8d0559874b780495cc0e451.png"), 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"), 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"), 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"), SAM_THE_WHALE(8391, "Sam the Whale", 30000, "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/f48a1887eb88238738996bb997b31c0f.png~tplv-obj.jpg"),
DIAMOND_TREE(7963, "Diamond Tree", 1088, "https://storage.streamdps.com/iblock/47a/47afc3c8563cacbff2ce13f2310a2fc4/84761a2a3e0431bda3bf3d2cc9d02b3f.webp"), DIAMOND_TREE(7963, "Diamond Tree", 1088, "https://storage.streamdps.com/iblock/47a/47afc3c8563cacbff2ce13f2310a2fc4/84761a2a3e0431bda3bf3d2cc9d02b3f.webp"),
@@ -913,12 +1155,20 @@ public enum Gift {
BIRD_WHISPERER(8344, "Bird Whisperer", 5000, "https://storage.streamdps.com/iblock/079/079bf5895816fb04293d01375eaf23a5/672128ca0f65deb0e75e2a9a690a79f0.webp"), BIRD_WHISPERER(8344, "Bird Whisperer", 5000, "https://storage.streamdps.com/iblock/079/079bf5895816fb04293d01375eaf23a5/672128ca0f65deb0e75e2a9a690a79f0.webp"),
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"), 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"), THE_MAGIC_LAMP(7161, "The Magic Lamp", 1000, "https://storage.streamdps.com/iblock/e0d/e0d45fccd69220f321531383d97f51fc/4296cc4b886f31bb5b2cf106ebf640ab.webp"),
TACO_(6113, "Taco ", 9, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/43d06db8c962623dbed6ecf70fb89ca8~tplv-obj.png"),
TIARA(8496, "Tiara", 299, "https://storage.streamdps.com/iblock/1b1/1b1ee7b697bae41ee2cbf834d1f1099e/303eec791a710c2417bb5075529681d9.webp"), 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(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"), COFFEE_5479(5479, "Coffee", 1, "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/02492214b9bd50fee2d69fd0d089c025.png~tplv-obj.jpg"),
@@ -927,6 +1177,8 @@ public enum Gift {
COFFEE_5961(5961, "Coffee", 30, "https://storage.streamdps.com/iblock/87b/87b2a811f1ca8c09060e2bd63a3b3be7/ae6288eb1eab67474807c64b9d69b5e6.webp"), 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(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"), HAND_HEART_6968(6968, "Hand Heart", 100, "https://storage.streamdps.com/iblock/9f0/9f0bfed08f1d3b9e852469d6a4debeda/519497b062ded1019c958d5d0b352a7e.webp"),
@@ -953,8 +1205,14 @@ public enum Gift {
OWL(5885, "Owl", 500, "https://storage.streamdps.com/iblock/e87/e87fc92de64aa711c6ce23ed3b2214c2/338e115665b1c9f75108b50a43adb95b.png"), OWL(5885, "Owl", 500, "https://storage.streamdps.com/iblock/e87/e87fc92de64aa711c6ce23ed3b2214c2/338e115665b1c9f75108b50a43adb95b.png"),
TOP_HOST(6194, "Top Host", 199, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/5947dc37282c417b411c61f20ee7d6d4~tplv-obj.png"),
TIKTOK_CROWN(8873, "TikTok Crown", 299, "https://storage.streamdps.com/iblock/a79/a790613bdf2e83725d0519bbf289529d/83bb670c15ab91b9192c50300f4c8054.webp"), 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"), ON_FIRE(6840, "On Fire", 200, "https://storage.streamdps.com/iblock/cba/cba95075d6b63b84fbc52abb9d1d8208/d93ecc0b966bf972f01e77339a68e124.webp"),
ON_FIRE_6958(6958, "On Fire", 200, "https://storage.streamdps.com/iblock/4ec/4ec314b4ee7dff4e92a8e1e75100dddf/19c9b5d8b5f24b1465632a31e55edca1.webp"), ON_FIRE_6958(6958, "On Fire", 200, "https://storage.streamdps.com/iblock/4ec/4ec314b4ee7dff4e92a8e1e75100dddf/19c9b5d8b5f24b1465632a31e55edca1.webp"),
@@ -971,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"), 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"), 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"), 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"), 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"), 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"), 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"), 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"), 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"), 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"), 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"), RAYA_RICE(6383, "Raya Rice", 1, "https://storage.streamdps.com/iblock/e0c/e0c375df5bdce1c926f46244ced54ecc/1bd688843c1c24370b8c4a74686c2c0d.png"),
@@ -1009,12 +1283,16 @@ public enum Gift {
GOLD_NECKLACE(5599, "Gold necklace", 200, "https://storage.streamdps.com/iblock/aa2/aa26035cd47797211a9ce1b5e51fd7ac/85e66a118c564c318e369974510f371d.png"), 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"), 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"), 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"), 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"), 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"), CAP(6104, "Cap", 99, "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/6c2ab2da19249ea570a2ece5e3377f04~tplv-obj.jpg"),
@@ -1031,12 +1309,16 @@ public enum Gift {
SUPERB_TEAM(8202, "Superb Team", 1, "https://storage.streamdps.com/iblock/e6e/e6e8cdd5e84f701dc627b8cc3e280d4c/29ce845878feb46152b20a75a3259d56.webp"), 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"), 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"), RAINING_GIFTS(8769, "Raining gifts", 999, "https://storage.streamdps.com/iblock/916/91661303a8dc3660acaf2f4e47a94f75/221a1f185676496ebcdbaf55f90aeb70.webp"),
NACHOS(7088, "Nachos", 9, "https://storage.streamdps.com/iblock/ff1/ff16cd1c796189ed8fcfdb019eb224ef/1ae8b0b05294c56b99197256fcaa3fd4.webp"), NACHOS(7088, "Nachos", 9, "https://storage.streamdps.com/iblock/ff1/ff16cd1c796189ed8fcfdb019eb224ef/1ae8b0b05294c56b99197256fcaa3fd4.webp"),
SNAG(6411, "Snag", 5, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/aa2d9b162c766a7fdf71fcead6d7bbcd~tplv-obj.png"),
APPETIZERS(6106, "Appetizers", 19, "https://storage.streamdps.com/iblock/76b/76b94aaced493a2448cf655b5468feaf/8c1bea41ec9fb547f0a0eb46d658a1c8.png"), APPETIZERS(6106, "Appetizers", 19, "https://storage.streamdps.com/iblock/76b/76b94aaced493a2448cf655b5468feaf/8c1bea41ec9fb547f0a0eb46d658a1c8.png"),
LOVE_LETTER(7932, "Love Letter", 1, "https://storage.streamdps.com/iblock/a40/a40cb58d5e8c07fa3e46a9acb4e34f6f/477507a1b14df0a22ef895c6214f3789.webp"), LOVE_LETTER(7932, "Love Letter", 1, "https://storage.streamdps.com/iblock/a40/a40cb58d5e8c07fa3e46a9acb4e34f6f/477507a1b14df0a22ef895c6214f3789.webp"),
@@ -1047,6 +1329,8 @@ public enum Gift {
BUMPER_CARS(5996, "Bumper Cars", 1288, "https://storage.streamdps.com/iblock/53b/53b569311552b729d1b347268370e576/8f236deca90a65e7046f7576d69976af.png"), BUMPER_CARS(5996, "Bumper Cars", 1288, "https://storage.streamdps.com/iblock/53b/53b569311552b729d1b347268370e576/8f236deca90a65e7046f7576d69976af.png"),
CAMPFIRE(5843, "Campfire", 388, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/e280eb1b7fe92b4efe612d98064d5a2d~tplv-obj.png"),
CAKE_SLICE(6784, "Cake Slice", 1, "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/f681afb4be36d8a321eac741d387f1e2~tplv-obj.jpg"), CAKE_SLICE(6784, "Cake Slice", 1, "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/f681afb4be36d8a321eac741d387f1e2~tplv-obj.jpg"),
CROCODILE(8740, "Crocodile", 10, "https://storage.streamdps.com/iblock/4e2/4e2d9df24c472158b8ed93546fc73b16/75722a173b75d601e0a80a679902529f.webp"), CROCODILE(8740, "Crocodile", 10, "https://storage.streamdps.com/iblock/4e2/4e2d9df24c472158b8ed93546fc73b16/75722a173b75d601e0a80a679902529f.webp"),
@@ -1063,8 +1347,12 @@ public enum Gift {
DIAMOND_CROWN_5604(5604, "Diamond Crown", 1499, "https://storage.streamdps.com/iblock/3b5/3b56c2352a02829ac4445094a3f76b51/738ad17c91919a940ee2001f9f262a95.png"), DIAMOND_CROWN_5604(5604, "Diamond Crown", 1499, "https://storage.streamdps.com/iblock/3b5/3b56c2352a02829ac4445094a3f76b51/738ad17c91919a940ee2001f9f262a95.png"),
COCONUT_DRINK(8225, "Coconut Drink", 5, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/ce27ad017f987240dc447e65ae866f4f~tplv-obj.png"),
M4_TROPHY(7544, "M4 Trophy", 450, "https://storage.streamdps.com/iblock/f40/f40a34a8e59806907deaa4f74df3462d/8deac28cb21517228bcd354645a987ea.webp"), M4_TROPHY(7544, "M4 Trophy", 450, "https://storage.streamdps.com/iblock/f40/f40a34a8e59806907deaa4f74df3462d/8deac28cb21517228bcd354645a987ea.webp"),
FLOWER(6034, "Flower", 299, "https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/9c20971eeb28b6b4ba37e57df3983da0~tplv-obj.png"),
PERFUME(5658, "Perfume", 20, "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/20b8f61246c7b6032777bb81bf4ee055~tplv-obj.jpg"), PERFUME(5658, "Perfume", 20, "https://p16-webcast.tiktokcdn.com/img/maliva/webcast-va/20b8f61246c7b6032777bb81bf4ee055~tplv-obj.jpg"),
KISS_YOUR_HEART(6661, "Kiss your Heart", 99, "https://storage.streamdps.com/iblock/13d/13d940df83e04a30523ca88c080ee8d8/213f06af314da4637a9ae8fc25bfaea3.webp"), KISS_YOUR_HEART(6661, "Kiss your Heart", 99, "https://storage.streamdps.com/iblock/13d/13d940df83e04a30523ca88c080ee8d8/213f06af314da4637a9ae8fc25bfaea3.webp"),
@@ -1079,6 +1367,8 @@ public enum Gift {
HOT_AIR_BALLOON(5976, "Hot Air Balloon", 999, "https://storage.streamdps.com/iblock/33a/33a5eb58a8dd71677072c9482aad209a/61be5fe5d3d639e3729edbf003a536c7.png"), 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(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"), TEA_6726(6726, "Tea", 20, "https://storage.streamdps.com/iblock/b0b/b0ba111b6319a8c9e384d5ca7b814e4c/6cd6f620512cd42711bc1235124b3265.webp"),
@@ -1091,6 +1381,8 @@ public enum Gift {
PIRATE_S_SHIP(7598, "Pirates Ship", 15000, "https://storage.streamdps.com/iblock/475/4753e54cae562b34edbf1a157cd60b21/722409ec69cfaf707d611b0987799296.webp"), 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"), 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"), BEACH_HUT(6719, "Beach Hut", 5000, "https://storage.streamdps.com/iblock/227/22716035cef6112f66035eca2b60fa31/ffce7cd46aaa6c2b27ccba610cf35a39.webp"),

View File

@@ -0,0 +1,82 @@
/*
* 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.models.http;
import lombok.Data;
import java.net.URI;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.*;
@Data
public class HttpData {
String url;
String method;
Map<String, List<String>> headers = new TreeMap<>();
Map<String, String> parameters = new TreeMap<>();
int status;
String body = "";
public static HttpData map(HttpRequest request) {
var data = new HttpData();
data.setUrl(request.uri().getPath());
data.setMethod(request.method());
data.setParameters(extractQueryParams(request.uri()));
data.setStatus(200);
if (request.bodyPublisher().isPresent()) {
data.setBody(request.bodyPublisher().get().toString());
}
data.setHeaders(Collections.unmodifiableMap(request.headers().map()));
return data;
}
public static HttpData map(HttpResponse<String> response) {
var data = new HttpData();
data.setUrl(response.uri().getPath());
data.setMethod(response.request().method());
data.setParameters(extractQueryParams(response.uri()));
data.setStatus(200);
data.setBody(response.body());
data.setHeaders(Collections.unmodifiableMap(response.headers().map()));
return data;
}
private static Map<String, String> extractQueryParams(URI uri) {
Map<String, String> params = new HashMap<>();
String query = uri.getQuery();
if (query != null && !query.isEmpty()) {
for (String param : query.split("&")) {
String[] keyValue = param.split("=");
if (keyValue.length > 1) {
params.put(keyValue[0], keyValue[1]);
} else {
params.put(keyValue[0], ""); // Empty value for parameter without explicit value
}
}
}
return params;
}
}

View File

@@ -43,11 +43,12 @@ public class User {
private long followers; private long followers;
private List<Badge> badges; private List<Badge> badges;
@Getter(AccessLevel.NONE) @Getter(AccessLevel.NONE)
private Set<UserAttribute> attributes; private final Set<UserAttribute> attributes = new HashSet<>();
public List<UserAttribute> getAttributes() { public List<UserAttribute> getAttributes() {
return attributes.stream().toList(); return attributes.stream().toList();
} }
public boolean hasAttribute(UserAttribute userFlag) { public boolean hasAttribute(UserAttribute userFlag) {
return attributes.contains(userFlag); return attributes.contains(userFlag);
} }
@@ -104,14 +105,28 @@ public class User {
this.following = following; this.following = following;
this.followers = followers; this.followers = followers;
this.badges = badges; this.badges = badges;
this.attributes = new HashSet<>(); }
public User(Long id,
String name,
String profileName,
Picture picture,
long following,
long followers,
List<Badge> badges) {
this.id = id;
this.name = name;
this.profileName = profileName;
this.picture = picture;
this.following = following;
this.followers = followers;
this.badges = badges;
} }
public User(Long userId, public User(Long userId,
String nickName) { String nickName) {
this.id = userId; this.id = userId;
this.name = nickName; this.name = nickName;
this.attributes = new HashSet<>();
} }
public User(Long userId, public User(Long userId,
@@ -194,4 +209,4 @@ public class User {
0, 0,
List.of(Badge.empty())); 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

@@ -0,0 +1,54 @@
/*
* 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.users.User;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Getter;
public class LiveData {
@Getter
@AllArgsConstructor
public static class Request {
private String roomId;
}
@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 LiveStatus {
HostNotFound,
HostOnline,
HostOffline,
}
}

View File

@@ -0,0 +1,60 @@
/*
* 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.AllArgsConstructor;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
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 enum UserStatus {
NotFound,
Offline,
LivePaused,
Live,
}
}

View File

@@ -0,0 +1,35 @@
/*
* 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.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class SignServerResponse
{
private String signedUrl;
private String userAgent;
}

View File

@@ -0,0 +1,111 @@
/*
* 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
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.setUseProxy(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.clone();
return newSettings;
}
}

View File

@@ -20,7 +20,9 @@
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * 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.time.Duration;
import java.util.HashMap; import java.util.HashMap;
@@ -28,41 +30,81 @@ import java.util.Map;
import java.util.TreeMap; import java.util.TreeMap;
import java.util.logging.Level; 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/";
/** private String clientLanguage;
* 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";
/** /**
* 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() { private Duration retryConnectionTimeout;
var clientSettings = new ClientSettings();
clientSettings.setTimeout(Duration.ofSeconds(DEFAULT_TIMEOUT)); /**
* 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.setClientLanguage("en-US");
clientSettings.setHandleExistingEvents(true);
clientSettings.setRetryOnConnectionFailure(false); clientSettings.setRetryOnConnectionFailure(false);
clientSettings.setRetryConnectionTimeout(Duration.ofSeconds(1)); clientSettings.setRetryConnectionTimeout(Duration.ofSeconds(1));
clientSettings.setPrintToConsole(false); clientSettings.setPrintToConsole(false);
clientSettings.setLogLevel(Level.ALL); clientSettings.setLogLevel(Level.ALL);
clientSettings.setClientParameters(Constants.DefaultClientParams());
clientSettings.setHttpSettings(httpSettings);
return clientSettings; return clientSettings;
} }
@@ -116,15 +158,15 @@ public class Constants {
public static Map<String, String> DefaultRequestHeaders() { public static Map<String, String> DefaultRequestHeaders() {
var headers = new HashMap<String, String>(); var headers = new HashMap<String, String>();
headers.put("authority","www.tiktok.com"); headers.put("authority", "www.tiktok.com");
headers.put("Connection", "keep-alive");
headers.put("Cache-Control", "max-age=0"); headers.put("Cache-Control", "max-age=0");
headers.put("Accept", "text/html,application/json,application/protobuf"); 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("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("Referer", "https://www.tiktok.com/");
headers.put("Origin", "https://www.tiktok.com"); headers.put("Origin", "https://www.tiktok.com");
headers.put("Accept-Language", "en-US,en; q=0.9"); headers.put("Accept-Language", "en-US,en; q=0.9");
headers.put("Accept-Encoding", "gzip, deflate");
return headers; return headers;
} }
} }

View File

@@ -0,0 +1,40 @@
/*
* 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;
//TODO proxy implementation
@Getter
public class ProxyClientSettings
{
@Setter
private boolean useProxy;
public ProxyClientSettings clone()
{
return new ProxyClientSettings();
}
}

View File

@@ -26,15 +26,20 @@ package io.github.jwdeveloper.tiktok.exceptions;
/** /**
* Happens when incoming data from TikTok can not be mapped to TikTokEvent's * Happens when incoming data from TikTok can not be mapped to TikTokEvent's
*/ */
public class TikTokMessageMappingException extends TikTokLiveException public class TikTokMessageMappingException extends TikTokLiveException {
{ public TikTokMessageMappingException(Class<?> inputClazz, Class<?> outputClass, Throwable throwable) {
public TikTokMessageMappingException(Class<?> inputClazz, Class<?> outputClass, Throwable throwable) super("Unable to handle mapping from class: " + inputClazz.getSimpleName() + " to class " + outputClass.getSimpleName(), throwable);
{
super("Unable to handle mapping from class: " + inputClazz.getSimpleName() + " to class " + outputClass.getSimpleName(),throwable);
} }
public TikTokMessageMappingException(Class<?> inputClazz, Class<?> outputClass, String message) public TikTokMessageMappingException(Class<?> inputClazz, Class<?> outputClass, String message) {
{ super("Unable to handle mapping from class: " + inputClazz.getSimpleName() + " to class " + outputClass.getSimpleName() + ": " + message);
super("Unable to handle mapping from class: " + inputClazz.getSimpleName() + " to class " + outputClass.getSimpleName()+": "+message); }
public TikTokMessageMappingException(Class<?> inputClazz, String message, Throwable throwable) {
super("Unable to handle mapping from class: " + inputClazz.getSimpleName() + ": " + message, throwable);
}
public TikTokMessageMappingException(String message, Throwable throwable) {
super( message, throwable);
} }
} }

View File

@@ -20,41 +20,26 @@
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ */
package io.github.jwdeveloper.tiktok.utils; package io.github.jwdeveloper.tiktok.exceptions;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveException; public class TikTokSignServerException extends TikTokLiveRequestException
public class CancelationToken
{ {
private boolean isCanceled =false; public TikTokSignServerException() {
public void cancel()
{
isCanceled =true;
} }
public boolean isCancel() public TikTokSignServerException(String message) {
{ super(message);
return isCanceled;
} }
public void throwIfCancel() public TikTokSignServerException(String message, Throwable cause) {
{ super(message, cause);
if(!isCanceled)
{
return;
}
throw new TikTokLiveException("Token requested cancelation");
} }
public boolean isNotCancel() public TikTokSignServerException(Throwable cause) {
{ super(cause);
return !isCancel();
} }
public static CancelationToken create() public TikTokSignServerException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
{ super(message, cause, enableSuppression, writableStackTrace);
return new CancelationToken();
} }
} }

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

@@ -22,6 +22,7 @@
*/ */
package io.github.jwdeveloper.tiktok.live; package io.github.jwdeveloper.tiktok.live;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent;
import io.github.jwdeveloper.tiktok.listener.ListenersManager; import io.github.jwdeveloper.tiktok.listener.ListenersManager;
import io.github.jwdeveloper.tiktok.listener.TikTokEventListener; import io.github.jwdeveloper.tiktok.listener.TikTokEventListener;
@@ -55,6 +56,11 @@ public interface LiveClient {
void disconnect(); void disconnect();
/**
* Use to manually invoke event
*/
void publishEvent(TikTokEvent event);
/** /**
* Get information about gifts * Get information about gifts
*/ */

View File

@@ -22,14 +22,32 @@
*/ */
package io.github.jwdeveloper.tiktok.live; package io.github.jwdeveloper.tiktok.live;
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.models.ConnectionState;
import java.util.List;
public interface LiveRoomInfo public interface LiveRoomInfo
{ {
/**
*
* @return get current count of viewers of live
*/
int getViewersCount(); int getViewersCount();
/**
*
* @return get total current count of viewers since beginning of live
*/
int getTotalViewersCount();
int getLikesCount(); int getLikesCount();
long getStartTime();
boolean isAgeRestricted(); boolean isAgeRestricted();
String getRoomId(); String getRoomId();
String getHostName(); String getHostName();
String getTitle();
User getHostUser();
List<RankingUser> getUsersRanking();
ConnectionState getConnectionState(); ConnectionState getConnectionState();
} }

View File

@@ -24,11 +24,10 @@ package io.github.jwdeveloper.tiktok.live.builder;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent; import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent;
import io.github.jwdeveloper.tiktok.data.events.*; import io.github.jwdeveloper.tiktok.data.events.*;
import io.github.jwdeveloper.tiktok.data.events.envelop.TikTokChestEvent;
import io.github.jwdeveloper.tiktok.data.events.gift.TikTokGiftComboEvent; import io.github.jwdeveloper.tiktok.data.events.gift.TikTokGiftComboEvent;
import io.github.jwdeveloper.tiktok.data.events.gift.TikTokGiftEvent; import io.github.jwdeveloper.tiktok.data.events.gift.TikTokGiftEvent;
import io.github.jwdeveloper.tiktok.data.events.room.TikTokRoomEvent; import io.github.jwdeveloper.tiktok.data.events.http.TikTokHttpResponseEvent;
import io.github.jwdeveloper.tiktok.data.events.room.TikTokRoomUserInfoEvent; import io.github.jwdeveloper.tiktok.data.events.room.TikTokRoomInfoEvent;
import io.github.jwdeveloper.tiktok.data.events.social.TikTokFollowEvent; import io.github.jwdeveloper.tiktok.data.events.social.TikTokFollowEvent;
import io.github.jwdeveloper.tiktok.data.events.social.TikTokJoinEvent; import io.github.jwdeveloper.tiktok.data.events.social.TikTokJoinEvent;
import io.github.jwdeveloper.tiktok.data.events.social.TikTokLikeEvent; import io.github.jwdeveloper.tiktok.data.events.social.TikTokLikeEvent;
@@ -40,58 +39,141 @@ import io.github.jwdeveloper.tiktok.data.events.websocket.TikTokWebsocketUnhandl
public interface EventsBuilder<T> { public interface EventsBuilder<T> {
T onRoom(EventConsumer<TikTokRoomEvent> event); /**
* Invoked whenever specified event is triggered
T onRoomUserInfo(EventConsumer<TikTokRoomUserInfoEvent> event); *
* @param eventClass event class
T onComment(EventConsumer<TikTokCommentEvent> event); * @param action action
*/
T onWebsocketMessage(EventConsumer<TikTokWebsocketMessageEvent> event); <E extends TikTokEvent> T onEvent(Class<E> eventClass, EventConsumer<E> action);
T onWebsocketResponse(EventConsumer<TikTokWebsocketResponseEvent> event);
T onWebsocketUnhandledMessage(EventConsumer<TikTokWebsocketUnhandledMessageEvent> event);
/**
* Invoked whenever any event is triggered
*
* @param action
* @return
*/
T onEvent(EventConsumer<TikTokEvent> action);
/**
* Invoked when information about room (live) got updated such as viewer count, etc..
*
* @param action
* @return
*/
T onRoomInfo(EventConsumer<TikTokRoomInfoEvent> action);
/**
* Invoked when someone send message to chat
*
* @param action
* @return
*/
T onComment(EventConsumer<TikTokCommentEvent> action);
T onGiftCombo(EventConsumer<TikTokGiftComboEvent> event); /**
T onGift(EventConsumer<TikTokGiftEvent> event); * Invoked when TikTokLiveJava makes http request and getting response
*
* @param action
* @return
*/
T onHttpResponse(EventConsumer<TikTokHttpResponseEvent> action);
T onQuestion(EventConsumer<TikTokQuestionEvent> event); /**
* Invoked when TikTok protocolBuffer data "message" was successfully mapped to event
* events contains protocol-buffer "Message" and TikTokLiveJava "Event"
*
* @param action
* @return
*/
T onWebsocketMessage(EventConsumer<TikTokWebsocketMessageEvent> action);
T onSubscribe(EventConsumer<TikTokSubscribeEvent> event); /**
* Invoked when there was not found event mapper for TikTok protocolBuffer data "message"
*
* @param action
* @return
*/
T onWebsocketUnhandledMessage(EventConsumer<TikTokWebsocketUnhandledMessageEvent> action);
T onFollow(EventConsumer<TikTokFollowEvent> event); /**
* Invoked every time TikTok sends protocolBuffer data to websocket
T onLike(EventConsumer<TikTokLikeEvent> event); * Response contains list of messages that are later mapped to events
* @param action
T onEmote(EventConsumer<TikTokEmoteEvent> event); * @return
*/
T onJoin(EventConsumer<TikTokJoinEvent> event); T onWebsocketResponse(EventConsumer<TikTokWebsocketResponseEvent> action);
T onShare(EventConsumer<TikTokShareEvent> event);
T onUnhandledSocial(EventConsumer<TikTokUnhandledSocialEvent> event);
// T onChest(EventConsumer<TikTokChestEvent> event);
T onLivePaused(EventConsumer<TikTokLivePausedEvent> event);
T onLiveEnded(EventConsumer<TikTokLiveEndedEvent> event);
T onConnected(EventConsumer<TikTokConnectedEvent> event);
T onReconnecting(EventConsumer<TikTokReconnectingEvent> event);
T onDisconnected(EventConsumer<TikTokDisconnectedEvent> event);
T onError(EventConsumer<TikTokErrorEvent> event);
T onEvent(EventConsumer<TikTokEvent> event);
/**
* Invoked for gifts that has no combo, or when combo finishes
* @param action
* @return
*/
T onGift(EventConsumer<TikTokGiftEvent> action);
/**
* Invoked for gifts that has combo options such as roses
* @param action
* @return
*/
T onGiftCombo(EventConsumer<TikTokGiftComboEvent> action);
T onQuestion(EventConsumer<TikTokQuestionEvent> action);
T onSubscribe(EventConsumer<TikTokSubscribeEvent> action);
T onFollow(EventConsumer<TikTokFollowEvent> action);
T onLike(EventConsumer<TikTokLikeEvent> action);
T onEmote(EventConsumer<TikTokEmoteEvent> action);
T onJoin(EventConsumer<TikTokJoinEvent> action);
T onShare(EventConsumer<TikTokShareEvent> action);
T onLivePaused(EventConsumer<TikTokLivePausedEvent> action);
T onLiveUnpaused(EventConsumer<TikTokLiveUnpausedEvent> action);
T onLiveEnded(EventConsumer<TikTokLiveEndedEvent> action);
/**
* Invoked when client has been successfully connected to live
* @param action
* @return
*/
T onConnected(EventConsumer<TikTokConnectedEvent> action);
/**
* Invoked when client tries to reconnect
* @param action
* @return
*/
T onReconnecting(EventConsumer<TikTokReconnectingEvent> action);
/**
* Invoked when client disconnected
* @param action
* @return
*/
T onDisconnected(EventConsumer<TikTokDisconnectedEvent> action);
/**
* Invoked when exception was throed inside client or event handler
* @param action
* @return
*/
T onError(EventConsumer<TikTokErrorEvent> action);
// TODO Figure out how those events works // TODO Figure out how those events works
// T onChest(EventConsumer<TikTokChestEvent> event);
//T onLinkMicFanTicket(TikTokEventConsumer<TikTokLinkMicFanTicketEvent> event); //T onLinkMicFanTicket(TikTokEventConsumer<TikTokLinkMicFanTicketEvent> event);
//T onEnvelope(TikTokEventConsumer<TikTokEnvelopeEvent> event); //T onEnvelope(TikTokEventConsumer<TikTokEnvelopeEvent> event);

View File

@@ -22,21 +22,59 @@
*/ */
package io.github.jwdeveloper.tiktok.live.builder; package io.github.jwdeveloper.tiktok.live.builder;
import io.github.jwdeveloper.tiktok.ClientSettings; import io.github.jwdeveloper.tiktok.data.settings.LiveClientSettings;
import io.github.jwdeveloper.tiktok.listener.TikTokEventListener; import io.github.jwdeveloper.tiktok.listener.TikTokEventListener;
import io.github.jwdeveloper.tiktok.live.LiveClient; import io.github.jwdeveloper.tiktok.live.LiveClient;
import io.github.jwdeveloper.tiktok.mappers.TikTokMapper;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer; import java.util.function.Consumer;
public interface LiveClientBuilder extends EventsBuilder<LiveClientBuilder> { public interface LiveClientBuilder extends EventsBuilder<LiveClientBuilder> {
LiveClientBuilder configure(Consumer<ClientSettings> consumer);
/**
* This method is triggered after default mappings are registered
* It could be used to OVERRIDE behaviour of mappings and implement custom behaviour
*
* Be aware if for example you override WebcastGiftEvent, onGiftEvent() will not be working
*
* @param onCustomMappings lambda method
* @return
*/
LiveClientBuilder onMapping(Consumer<TikTokMapper> onCustomMappings);
/**
* Configuration of client settings
* @see LiveClientSettings
* @param onConfigure
* @return
*/
LiveClientBuilder configure(Consumer<LiveClientSettings> onConfigure);
/**
* @see TikTokEventListener
* Adding events listener class, its fancy way to register events without using lamda method
* but actual method in class that implements TikTokEventListener
* @return
*/
LiveClientBuilder addListener(TikTokEventListener listener); LiveClientBuilder addListener(TikTokEventListener listener);
/**
*
* @return LiveClient object
*/
LiveClient build(); LiveClient build();
/**
*
* @return LiveClient object and connects to TikTok live
*/
LiveClient buildAndConnect(); LiveClient buildAndConnect();
/**
*
* @return LiveClient object and connects to TikTok live asynchronously
*/
CompletableFuture<LiveClient> buildAndConnectAsync(); CompletableFuture<LiveClient> buildAndConnectAsync();
} }

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.mappers;
import com.google.protobuf.GeneratedMessageV3;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent;
import io.github.jwdeveloper.tiktok.mappers.data.MappingAction;
import io.github.jwdeveloper.tiktok.mappers.data.MappingResult;
import java.util.function.Function;
public interface TikTokMapper {
/**
* * if mapper is not found for messageName, TikTokLiveException is thrown
*
* @param messageName
* @return TikTokMapperModel
*/
TikTokMapperModel forMessage(String messageName);
TikTokMapperModel forMessage(Class<? extends GeneratedMessageV3> mapperName);
TikTokMapperModel forMessage(String mapperName, MappingAction<MappingResult> onMapping);
TikTokMapperModel forMessage(Class<? extends GeneratedMessageV3> mapperName, MappingAction<MappingResult> onMapping);
TikTokMapperModel forMessage(Class<? extends GeneratedMessageV3> mapperName, Function<byte[], TikTokEvent> onMapping);
TikTokMapperModel forAnyMessage();
boolean isRegistered(String mapperName);
<T extends GeneratedMessageV3> boolean isRegistered(Class<T> mapperName);
}

View File

@@ -0,0 +1,68 @@
/*
* 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.mappers;
import com.google.protobuf.GeneratedMessageV3;
import io.github.jwdeveloper.tiktok.exceptions.TikTokMessageMappingException;
import io.github.jwdeveloper.tiktok.utils.ProtoBufferObject;
public interface TikTokMapperHelper {
/**
* @param bytes protocol buffer data bytes
* @param messageClass class that we want to serialize bytes to
* @param <T> @messageClass must be class that is made by protocol buffer
* @return object of type @messageClass
*/
<T extends GeneratedMessageV3> T bytesToWebcastObject(byte[] bytes, Class<T> messageClass);
/**
* @param bytes protocol buffer data bytes
* @param messageName class that we want to serialize bytes to
* @return protocol buffer objects if class for @messageName has been found
* @throws TikTokMessageMappingException if there is no suitable class for messageName
*/
Object bytesToWebcastObject(byte[] bytes, String messageName);
/**
* @param messageName checks wheaten TikTokLiveJava has class representation for certain protocol-buffer message name
* @return false if class is not found
*/
boolean isMessageHasProtoClass(String messageName);
/**
* @param bytes protocol buffer data bytes
* @return tree structure of protocol buffer object
* @see ProtoBufferObject
*/
ProtoBufferObject bytesToProtoBufferStructure(byte[] bytes);
/**
* Converts object to json
*
* @param obj any object
* @return pretty formatted json
*/
String toJson(Object obj);
}

View File

@@ -20,36 +20,39 @@
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ */
package io.github.jwdeveloper.tiktok.data.events.room; package io.github.jwdeveloper.tiktok.mappers;
import io.github.jwdeveloper.tiktok.annotations.EventMeta; import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent;
import io.github.jwdeveloper.tiktok.annotations.EventType; import io.github.jwdeveloper.tiktok.mappers.data.AfterMappingAction;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokHeaderEvent; import io.github.jwdeveloper.tiktok.mappers.data.MappingAction;
import io.github.jwdeveloper.tiktok.data.models.RankingUser; import io.github.jwdeveloper.tiktok.mappers.data.MappingResult;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastRoomUserSeqMessage;
import lombok.Getter;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.function.Function;
public interface TikTokMapperModel {
@Getter
@EventMeta(eventType = EventType.Message)
public class TikTokRoomUserInfoEvent extends TikTokHeaderEvent {
private final int totalUsers;
/** /**
* Only top 5 users in ranking has detailed data * @return name of websocket message that this mapper is mapping from
* rest has only ID
*/ */
private final List<RankingUser> usersRanking;
public TikTokRoomUserInfoEvent(WebcastRoomUserSeqMessage msg) {
super(msg.getCommon());
totalUsers = msg.getTotalUser();
usersRanking = msg.getRanksListList().stream().map(RankingUser::new)
.sorted((ru1, ru2) -> Integer.compare(ru2.getScore(), ru1.getScore()))
.collect(Collectors.toList());
}
String getSourceMessageName();
/**
* @param action Input bytes from websocket, you can modify it and returns different bytes
*/
TikTokMapperModel onBeforeMapping(MappingAction<byte[]> action);
/**
* @param action Input bytes from websocket. As output returns list of tiktok live events
*/
TikTokMapperModel onMapping(MappingAction<MappingResult> action);
/**
* @param action You can modify output list of TikTokLive events
* @see AfterMappingAction
*/
TikTokMapperModel onAfterMapping(Function<MappingResult, List<TikTokEvent>> action);
} }

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.mappers.data;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent;
import java.util.List;
@FunctionalInterface
public interface AfterMappingAction {
/**
* @param source object that was used as source to create events
* @param events list of events prepared before, could be modified or changed
* @return list of events that will be invoked
*/
List<TikTokEvent> onAfterMapping(Object source, List<TikTokEvent> events);
}

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.mappers.data;
import io.github.jwdeveloper.tiktok.mappers.TikTokMapperHelper;
@FunctionalInterface
public interface MappingAction<T> {
/**
* @param inputBytes incoming bytes from TikTok server. The represents protocol buffer message that was send to client
* @param messageName name of protocol buffer message
* @param mapperHelper utils and helper methods that can be use to debbug/display/deserialize protocol buffer data
* @return
*/
T onMapping(byte[] inputBytes, String messageName, TikTokMapperHelper mapperHelper);
}

View File

@@ -0,0 +1,53 @@
/*
* 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.mappers.data;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.List;
@AllArgsConstructor
@Getter
public class MappingResult
{
Object source;
List<TikTokEvent> events;
String message;
public static MappingResult of(Object source) {
return new MappingResult(source, List.of(),"");
}
public static MappingResult of(Object source, List<TikTokEvent> events) {
return new MappingResult(source, events,"");
}
public static MappingResult of(Object source,TikTokEvent events) {
return new MappingResult(source, List.of(events),"");
}
}

View File

@@ -25,8 +25,10 @@ package io.github.jwdeveloper.tiktok.utils;
import com.google.gson.ExclusionStrategy; import com.google.gson.ExclusionStrategy;
import com.google.gson.FieldAttributes; import com.google.gson.FieldAttributes;
import com.google.gson.GsonBuilder; import com.google.gson.GsonBuilder;
import io.github.jwdeveloper.tiktok.data.dto.MessageMetaData;
import java.awt.*; import java.awt.*;
import java.time.Duration;
import java.util.ArrayList; import java.util.ArrayList;
public class JsonUtil { public class JsonUtil {
@@ -34,13 +36,25 @@ public class JsonUtil {
return new GsonBuilder() return new GsonBuilder()
.addSerializationExclusionStrategy(new ExclusionStrategy() { .addSerializationExclusionStrategy(new ExclusionStrategy() {
@Override @Override
public boolean shouldSkipField(FieldAttributes fieldAttributes) { public boolean shouldSkipField(FieldAttributes fieldAttributes)
{
return false; return false;
} }
@Override @Override
public boolean shouldSkipClass(Class<?> aClass) { public boolean shouldSkipClass(Class<?> aClass)
return aClass.equals(Image.class); {
if(aClass.equals(Image.class))
{
return true;
}
if(aClass.equals(MessageMetaData.class))
{
return true;
}
return false;
} }
}) })
.disableHtmlEscaping() .disableHtmlEscaping()

View File

@@ -0,0 +1,78 @@
/*
* 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.utils;
import java.nio.file.Path;
import java.util.Map;
import java.util.TreeMap;
public class ProtoBufferFileGenerator {
public static String generate(ProtoBufferObject protoBuffObj, String name) {
var sb = new StringBuilder();
var offset = 2;
sb.append("message ").append(name).append(" { \n");
var objects = new TreeMap<String, ProtoBufferObject>();
var objectCounter = 0;
for (var entry : protoBuffObj.getFields().entrySet()) {
var index = entry.getKey();
var field = entry.getValue();
var fieldName = field.type.toLowerCase() + "Value";
var value = field.value;
if (field.value instanceof ProtoBufferObject object) {
fieldName += objectCounter;
value = "";
objects.put(fieldName,object);
objectCounter++;
}
for (var i = 0; i < offset; i++) {
sb.append(" ");
}
sb.append(field.type).append(" ").append(fieldName)
.append(" ")
.append("=")
.append(" ")
.append(index)
.append(";")
.append(" //")
.append(value)
.append("\n");
}
sb.append(" \n");
for(var object : objects.entrySet())
{
var structure = generate(object.getValue(),object.getKey());
sb.append(structure);
}
sb.append(" \n");
sb.append("} \n");
return sb.toString();
}
}

View File

@@ -0,0 +1,53 @@
/*
* 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.utils;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonObject;
public class ProtoBufferJsonGenerator {
public static JsonObject genratejson(ProtoBufferObject protoBuffObj) {
JsonObject jsonObject = new JsonObject();
for (var entry : protoBuffObj.getFields().entrySet()) {
var fieldName = entry.getKey() + "_" + entry.getValue().type;
if (entry.getValue().value instanceof ProtoBufferObject protoObj)
{
JsonObject childJson = genratejson(protoObj);
jsonObject.add(fieldName, childJson);
continue;
}
var value = entry.getValue().value.toString();
jsonObject.addProperty(fieldName, value);
}
return jsonObject;
}
public static String generate(ProtoBufferObject protoBufferObject) {
var json = genratejson(protoBufferObject);
var gson = new GsonBuilder().setPrettyPrinting().create();
return gson.toJson(json);
}
}

View File

@@ -0,0 +1,103 @@
/*
* 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.utils;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Map;
import java.util.TreeMap;
public class ProtoBufferObject {
@Getter
private final Map<Integer, ProtoBufferField> fields;
public 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));
}
public void addField(int index, ProtoBufferField value) {
fields.put(index, value);
}
public String toProtoFile()
{
return ProtoBufferFileGenerator.generate(this,"UnknownMessage");
}
public String toJson()
{
return ProtoBufferJsonGenerator.generate(this);
}
@Override
public String toString() {
return toString(0, true);
}
public String toString(int offset ,boolean nested) {
var sb = new StringBuilder();
sb.append("\n");
for (var entry : fields.entrySet()) {
var index = entry.getKey();
var field = entry.getValue();
for(var i =0;i<offset;i++)
{
sb.append(" ");
}
sb.append(index).append(" ")
.append(field.type).append(" ");
var value = field.value;
if (value instanceof ProtoBufferObject child) {
sb.append(child.toString(offset+2,nested));
} else {
sb.append(entry.getValue().value);
}
sb.append("\n");
}
return sb.toString();
}
@AllArgsConstructor
public class ProtoBufferField {
public String type;
public Object value;
}
}

View File

@@ -0,0 +1,116 @@
/*
* 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.utils;
import com.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.UnknownFieldSet;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastResponse;
import java.util.Base64;
public class ProtocolUtils {
public static String toBase64(byte[] bytes) {
return Base64.getEncoder().encodeToString(bytes);
}
public static String toBase64(WebcastResponse.Message bytes) {
return Base64.getEncoder().encodeToString(bytes.toByteArray());
}
public static byte[] fromBase64(String base64) {
return Base64.getDecoder().decode(base64);
}
public static ProtoBufferObject getProtocolBufferStructure(byte[] bytes) {
try {
var files = UnknownFieldSet.parseFrom(bytes);
var protoBufferObject = new ProtoBufferObject();
createStructure(files, protoBufferObject);
return protoBufferObject;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static void createStructure(UnknownFieldSet unknownFieldSet, ProtoBufferObject root) throws InvalidProtocolBufferException {
for (var entry : unknownFieldSet.asMap().entrySet()) {
var objectValue = entry.getValue();
var type = "undefind";
Object value = null;
var index = entry.getKey();
if (!objectValue.getLengthDelimitedList().isEmpty()) {
var nestedObject = new ProtoBufferObject();
for (var str : objectValue.getLengthDelimitedList()) {
try {
var nestedFieldsSet = UnknownFieldSet.parseFrom(str);
createStructure(nestedFieldsSet, nestedObject);
} catch (Exception e)
{
type = "string";
value = str.toStringUtf8();
}
}
if (value != null) {
root.addField(index, "string", value);
} else {
root.addField(index, "object", nestedObject);
}
continue;
}
if (!objectValue.getFixed32List().isEmpty()) {
type = "Fixed32List";
value = objectValue.getFixed32List();
}
if (!objectValue.getFixed64List().isEmpty()) {
type = "Fixed64List";
value = objectValue.getFixed64List();
}
if (!objectValue.getGroupList().isEmpty()) {
type = "getGroupList";
value = objectValue.getGroupList();
}
if (!objectValue.getVarintList().isEmpty()) {
type = "int";
value = objectValue.getVarintList().get(0);
}
root.addField(index, type, value);
}
}
public static WebcastResponse.Message fromBase64ToMessage(String base64) throws InvalidProtocolBufferException {
var bytes = fromBase64(base64);
return WebcastResponse.Message.parseFrom(bytes);
}
}

View File

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

View File

@@ -64,8 +64,8 @@ message Text {
string stringValue = 11; string stringValue = 11;
oneof textPieceType oneof textPieceType
{ {
TextPieceUser userValue = 21; TextPieceUser userValue = 21;
TextPieceGift giftValue = 22; TextPieceGift giftValue = 22;
} }
TextPiecePatternRef patternRefValue = 24; TextPiecePatternRef patternRefValue = 24;
} }
@@ -155,7 +155,7 @@ message BadgeStruct {
message ProfileCardPanel { message ProfileCardPanel {
bool useNewProfileCardStyle = 1; bool useNewProfileCardStyle = 1;
// BadgeTextPosition badgeTextPosition = 2; // Enum // BadgeTextPosition badgeTextPosition = 2; // Enum
ProjectionConfig projectionConfig = 3; ProjectionConfig projectionConfig = 3;
ProfileContent profileContent = 4; ProfileContent profileContent = 4;
} }
@@ -973,4 +973,567 @@ message FanTicketRoomNoticeContent {
int64 MatchId = 3; int64 MatchId = 3;
int64 EventTime = 4; int64 EventTime = 4;
string FanTicketIconUrl = 5; string FanTicketIconUrl = 5;
}
message LinkerAcceptNoticeContent {
int64 fromUserId = 1;
int64 fromRoomId = 2;
int64 toUserId = 3;
}
message LinkerCancelContent {
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;
}
message LinkerEnterContent {
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;
}
message LinkerKickOutContent {
int64 fromUserId = 1;
KickoutReason kickoutReason = 2; // Enum
}
message LinkerLeaveContent {
int64 userId = 1;
string linkmicIdStr = 2;
int64 sendLeaveUid = 3;
int64 leaveReason = 4;
}
//Empty
message LinkerLinkedListChangeContent {
}
//Empty
message CohostListChangeContent {
}
message LinkerListChangeContent {
repeated ListUser linkedUsersList = 1;
repeated ListUser appliedUsersList = 2;
repeated ListUser connectingUsersList = 3;
}
message LinkerMediaChangeContent {
int64 op = 1; // Enum
int64 toUserId = 2;
int64 anchorId = 3;
int64 roomId = 4;
int64 changeScene = 5; // Enum
}
//Empty
message LinkerMicIdxUpdateContent {
}
message LinkerMuteContent {
int64 userId = 1;
int64 status = 2; // Enum
}
message LinkerRandomMatchContent {
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;
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 {
int64 MaxMemberLimit = 1;
int64 LinkType = 2;
int64 Scene = 3;
int64 OwnerUserId = 4;
int64 OwnerRoomId = 5;
int64 Vendor = 6;
}
message LinkerSysKickOutContent {
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;
}
//Empty
message LinkerUpdateUserSettingContent {
}
//Empty
message LinkerWaitingListChangeContent {
}
message LinkmicUserSettingInfo {
int64 userId = 1;
int64 layout = 2; // @warning Enum not found, should be Layout
int64 fixMicNum = 3; // @warning Enum not found, should be FixMicNum
int64 allowRequestFromUser = 4; // @warning Enum not found, should be AllowRequestFromUser
int64 allowRequestFromFollowerOnly = 5; // @warning Enum not found, should be AllowRequestFromFollowerOnly
LinkmicApplierSortSetting applierSortSetting = 7; // Enum
}
message Player {
int64 roomId = 1;
int64 userId = 2;
}
message AllListUser {
repeated LinkLayerListUser linkedList = 2;
repeated LinkLayerListUser appliedList = 3;
repeated LinkLayerListUser invitedList = 4;
repeated LinkLayerListUser readyList = 5;
}
message LinkLayerListUser {
User user = 1;
int64 linkmicId = 2;
Position pos = 3;
int64 linkedTimeNano = 4;
string appVersion = 5;
int64 magicNumber1 = 7;
}
message Position {
int32 type = 1;
LinkPosition link = 2;
}
message LinkPosition {
int32 position = 1;
int32 opt = 2;
}
message GroupPlayer {
int64 channelId = 1;
User user = 2;
}
message DSLConfig {
int32 sceneVersion = 1;
string layoutId = 2;
}
message GroupChannelAllUser {
int64 groupChannelId = 1;
repeated GroupChannelUser userList = 2;
}
message GroupChannelUser {
int64 channelId = 1;
GroupStatus status = 2; // Enum
TextType type = 3; // Enum
AllListUser allUser = 4;
int64 joinTime = 5;
int64 linkedTime = 6;
GroupPlayer ownerUser = 7;
}
message RTCExtraInfo {
RTCEngineConfig liveRtcEngineConfig = 1;
repeated RTCLiveVideoParam liveRtcVideoParamList = 2;
RTCBitrateMap rtcBitrateMap = 3;
int32 rtcFps = 4;
string rtcBusinessId = 8;
int32 interactClientType = 10;
message RTCEngineConfig {
string rtcAppId = 1;
string rtcUserId = 2;
string rtcToken = 3;
int64 rtcChannelId = 4;
}
message RTCLiveVideoParam {
int32 strategyId = 1;
RTCVideoParam params = 2;
}
message RTCVideoParam {
int32 width = 1;
int32 height = 2;
int32 fps = 3;
int32 bitrateKbps = 4;
}
message RTCBitrateMap {
int32 xx1 = 1;
int32 xx2 = 2;
int32 xx3 = 3;
int32 xx4 = 4;
}
}
message CreateChannelContent {
Player owner = 1;
string ownerLinkMicId = 2;
}
message ListChangeContent {
TextType type = 1; // Enum
AllListUser list = 2;
}
message MultiLiveContent {
InviteBizContent inviteBizContent = 2;
ReplyBizContent replyBizContent = 3;
PermitBizContent permitBizContent = 4;
KickOutBizContent kickOutBizContent = 6;
message InviteBizContent {
LinkmicUserSettingInfo anchorSettingInfo = 1;
int64 inviteSource = 2; // @warning Enum not found, should be InviteSource
User operatorUserInfo = 3;
int64 operatorLinkAdminType = 4; // @warning Enum not found, should be OperatorLinkAdminType
User inviteeUserInfo = 5;
}
message ReplyBizContent {
int32 linkType = 1;
int32 isTurnOffInvitation = 2;
User replyUserInfo = 3;
}
message PermitBizContent {
LinkmicUserSettingInfo anchorSettingInfo = 1;
int64 expireTimestamp = 2;
User operatorUserInfo = 3;
int64 operatorLinkAdminType = 4; // @warning Enum not found, should be OperatorLinkAdminType
}
message KickOutBizContent {
User operatorUserInfo = 1;
int64 operatorLinkAdminType = 2; // @warning Enum not found, should be OperatorLinkAdminType
User kickPlayerUserInfo = 3;
}
}
message InviteContent {
Player invitor = 1;
RTCExtraInfo inviteeRtcExtInfo = 2;
string invitorLinkMicId = 3;
string inviteeLinkMicId = 4;
bool isOwner = 5;
Position pos = 6;
DSLConfig dsl = 7;
User invitee = 8;
User operator = 9;
}
// @ApplyContent
// proto.webcast.im
// C:\Users\ja\RiderProjects\TikTokProBufferGenerator\Application\output\sources\test.js
message ApplyContent {
Player applier = 1;
string applierLinkMicId = 2;
}
// @PermitApplyContent
// proto.webcast.im
// C:\Users\ja\RiderProjects\TikTokProBufferGenerator\Application\output\sources\test.js
message PermitApplyContent {
Player permiter = 1;
string permiterLinkMicId = 2;
Position applierPos = 3;
ReplyStatus replyStatus = 4; // Enum
DSLConfig dsl = 5;
User applier = 6;
User operator = 7;
string applierLinkMicId = 8;
}
// @ReplyInviteContent
// proto.webcast.im
// C:\Users\ja\RiderProjects\TikTokProBufferGenerator\Application\output\sources\test.js
message ReplyInviteContent {
Player invitee = 1;
ReplyStatus replyStatus = 2; // Enum
string inviteeLinkMicId = 3;
Position inviteePos = 4;
Player inviteOperatorUser = 5;
}
// @KickOutContent
// proto.webcast.im
// C:\Users\ja\RiderProjects\TikTokProBufferGenerator\Application\output\sources\test.js
message KickOutContent {
Player offliner = 1;
KickoutReason kickoutReason = 2; // Enum
}
// @CancelApplyContent
// proto.webcast.im
// C:\Users\ja\RiderProjects\TikTokProBufferGenerator\Application\output\sources\test.js
message CancelApplyContent {
Player applier = 1;
string applierLinkMicId = 2;
}
// @CancelInviteContent
// proto.webcast.im
// C:\Users\ja\RiderProjects\TikTokProBufferGenerator\Application\output\sources\test.js
message CancelInviteContent {
Player invitor = 1;
string invitorLinkMicId = 2;
string inviteeLinkMicId = 3;
int64 inviteSeqId = 4;
Player invitee = 5;
}
// @LeaveContent
// proto.webcast.im
// C:\Users\ja\RiderProjects\TikTokProBufferGenerator\Application\output\sources\test.js
message LeaveContent {
Player leaver = 1;
int64 leaveReason = 2;
}
// @FinishChannelContent
// proto.webcast.im
// C:\Users\ja\RiderProjects\TikTokProBufferGenerator\Application\output\sources\test.js
message FinishChannelContent {
Player owner = 1;
int64 finishReason = 2;
}
// @JoinDirectContent
// proto.webcast.im
// C:\Users\ja\RiderProjects\TikTokProBufferGenerator\Application\output\sources\test.js
message JoinDirectContent {
LinkLayerListUser joiner = 1;
AllListUser allUsers = 2;
}
// @LeaveJoinGroupContent
// proto.webcast.im
// C:\Users\ja\RiderProjects\TikTokProBufferGenerator\Application\output\sources\test.js
message LeaveJoinGroupContent {
GroupPlayer operator = 1;
int64 groupChannelId = 2;
string leaveSource = 3;
}
// @PermitJoinGroupContent
// proto.webcast.im
message PermitJoinGroupContent {
GroupPlayer approver = 1;
AgreeStatus agreeStatus = 2; // Enum
TextType type = 3; // Enum
repeated RTCExtraInfo groupExtInfoList = 4;
GroupChannelAllUser groupUser = 5;
}
// @CancelJoinGroupContent
// proto.webcast.im
message CancelJoinGroupContent {
repeated GroupPlayer leaverList = 1;
GroupPlayer operator = 2;
TextType type = 3; // Enum
}
message P2PGroupChangeContent {
repeated RTCExtraInfo groupExtInfoList = 1;
GroupChannelAllUser groupUser = 2;
}
message BusinessContent {
int64 overLength = 1;
MultiLiveContent multiLiveContent = 100;
CohostContent cohostContent = 200;
message CohostContent {
JoinGroupBizContent joinGroupBizContent = 1;
}
message JoinGroupBizContent {
int32 fromRoomAgeRestricted = 1;
Tag fromTag = 2;
PerceptionDialogInfo dialog = 3;
PunishEventInfo punishInfo = 4;
JoinGroupMessageExtra joinGroupMsgExtra = 101;
}
message Tag {
int32 tagType = 1;
string tagValue = 2;
string tagText = 3;
}
message PerceptionDialogInfo {
int64 iconType = 1; // @warning Enum not found, should be IconType
Text title = 2;
Text subTitle = 3;
Text adviceActionText = 4;
Text defaultActionText = 5;
string violationDetailUrl = 6;
int32 scene = 7;
int64 targetUserId = 8;
int64 targetRoomId = 9;
int64 countDownTime = 10;
bool showFeedback = 11;
repeated PerceptionFeedbackOption feedbackOptionsList = 12;
int64 policyTip = 13;
}
message PerceptionFeedbackOption {
int64 id = 1;
string contentKey = 2;
}
message JoinGroupMessageExtra {
int64 sourceType = 1;
RivalExtra extra = 2;
repeated RivalExtra otherUsersList = 3;
// @RivalExtra
// proto.webcast.im.JoinGroupMessageExtra
// C:\Users\ja\RiderProjects\TikTokProBufferGenerator\Application\output\sources\test.js
message RivalExtra {
int64 userCount = 4;
Image avatarThumb = 5;
string displayId = 6;
AuthenticationInfo authenticationInfo = 7;
string nickname = 8;
int64 followStatus = 9;
Hashtag hashtag = 10;
TopHostInfo topHostInfo = 11;
int64 userId = 12;
bool isBestTeammate = 13;
message AuthenticationInfo {
string customVerify = 1;
string enterpriseVerifyReason = 2;
Image authenticationBadge = 3;
}
}
}
message Hashtag {
int64 id = 1;
string title = 2;
Image image = 3;
HashtagNamespace namespace = 4; // Enum
}
message TopHostInfo {
string rankType = 1;
int64 topIndex = 2;
}
}
message JoinGroupContent {
GroupChannelAllUser groupUser = 1;
GroupPlayer joinUser = 2;
TextType type = 3; // Enum
} }

View File

@@ -25,6 +25,85 @@ enum EmotePrivateType {
EMOTE_PRIVATE_TYPE_SUB_WAVE = 1; EMOTE_PRIVATE_TYPE_SUB_WAVE = 1;
} }
enum TextType {
DISPLAY_TEXT = 0;
CONTENT = 1;
}
enum LinkmicApplierSortSetting {
LINKMIC_APPLIER_SORT_SETTING_NONE = 0;
LINKMIC_APPLIER_SORT_SETTING_BY_GIFT_SCORE = 1;
}
enum HashtagNamespace {
GLOBAL = 0;
GAMING = 1;
}
enum AgreeStatus {
AGREE_UNKNOWN = 0;
AGREE = 1;
REJECT = 2;
}
enum KickoutReason {
KICKOUT_REASON_UNKNOWN = 0;
KICKOUT_REASON_FIRST_FRAME_TIMEOUT = 1;
KICKOUT_REASON_BY_HOST = 2;
KICKOUT_REASON_RTC_LOST_CONNECTION = 3;
KICKOUT_REASON_BY_PUNISH = 4;
KICKOUT_REASON_BY_ADMIN = 5;
KICKOUT_REASON_HOST_REMOVE_ALL_GUESTS = 6;
}
enum GroupStatus {
GROUP_STATUS_UNKNOWN = 0;
GROUP_STATUS_WAITING = 1;
GROUP_STATUS_LINKED = 3;
}
enum BusinessCase {
BUSINESS_NOT_SET = 0;
APPLY_BIZ_CONTENT = 1;
INVITE_BIZ_CONTENT = 2;
REPLY_BIZ_CONTENT = 3;
PERMIT_BIZ_CONTENT = 4;
JOIN_DIRECT_BIZ_CONTENT = 5;
KICK_OUT_BIZ_CONTENT = 6;
LIST_CHANGE_BIZ_CONTENT = 11;
MULTI_LIVE_CONTENT = 100;
COHOST_CONTENT = 200;
}
enum ReplyStatus {
REPLY_STATUS_UNKNOWN = 0;
REPLY_STATUS_AGREE = 1;
REPLY_STATUS_REFUSE_PERSONALLY = 2;
REPLY_STATUS_REFUSE_TYPE_NOT_SUPPORT = 3;
REPLY_STATUS_REFUSE_PROCESSING_INVITATION = 4;
REPLY_STATUS_REFUSE_BY_TIMEOUT = 5;
REPLY_STATUS_REFUSE_EXCEPTION = 6;
REPLY_STATUS_REFUSE_SYSTEM_NOT_SUPPORTED = 7;
REPLY_STATUS_REFUSE_SUBTYPE_DIFFERENCE = 8;
REPLY_STATUS_REFUSE_IN_MICROOM = 9;
REPLY_STATUS_REFUSE_NOT_LOAD_PLUGIN = 10;
REPLY_STATUS_REFUSE_IN_MULTI_GUEST = 11;
REPLY_STATUS_REFUSE_PAUSE_LIVE = 12;
REPLY_STATUS_REFUSE_OPEN_CAMERA_DIALOG_SHOWING = 13;
REPLY_STATUS_REFUSE_DRAW_GUESSING = 14;
REPLY_STATUS_REFUSE_RANDOM_MATCHING = 15;
REPLY_STATUS_REFUSE_IN_MATCH_PROCESSING = 16;
REPLY_STATUS_REFUSE_IN_MICROOM_FOR_MULTI_COHOST = 17;
REPLY_STATUS_REFUSE_COHOST_FINISHED = 18;
REPLY_STATUS_REFUSE_NOT_CONNECTED = 19;
REPLY_STATUS_REFUSE_LINKMIC_FULL = 20;
REPLY_STATUS_REFUSE_ARC_INCOMPATIBLE = 21;
REPLY_STATUS_REFUSE_PROCESSING_OTHER_INVITE = 22;
REPLY_STATUS_REFUSE_PROCESSING_OTHER_APPLY = 23;
REPLY_STATUS_REFUSE_IN_ANCHOR_COHOST = 24;
REPLY_STATUS_REFUSE_TOPIC_PAIRING = 25;
}
enum SubscribeType { enum SubscribeType {
SUBSCRIBETYPE_ONCE = 0; SUBSCRIBETYPE_ONCE = 0;
@@ -68,6 +147,7 @@ enum MemberMessageAction {
enum ControlAction { enum ControlAction {
ControlActionUNKNOWN = 0; ControlActionUNKNOWN = 0;
STREAM_PAUSED = 1; // Stream Paused by Host STREAM_PAUSED = 1; // Stream Paused by Host
STREAM_UNPAUSED = 2;
STREAM_ENDED = 3; // Stream Ended by Host STREAM_ENDED = 3; // Stream Ended by Host
} }
@@ -135,4 +215,80 @@ enum EnvelopeDisplay
EnvelopeDisplayUnknown = 0; EnvelopeDisplayUnknown = 0;
EnvelopeDisplayNew = 1; EnvelopeDisplayNew = 1;
EnvelopeDisplayHide = 2; EnvelopeDisplayHide = 2;
}
enum CommonContentCase {
COMMON_CONTENT_NOT_SET = 0;
CREATE_CHANNEL_CONTENT = 100;
LIST_CHANGE_CONTENT = 102;
INVITE_CONTENT = 103;
APPLY_CONTENT = 104;
PERMIT_APPLY_CONTENT = 105;
REPLY_INVITE_CONTENT = 106;
KICK_OUT_CONTENT = 107;
CANCEL_APPLY_CONTENT = 108;
CANCEL_INVITE_CONTENT = 109;
LEAVE_CONTENT = 110;
FINISH_CONTENT = 111;
JOIN_DIRECT_CONTENT = 112;
JOIN_GROUP_CONTENT = 113;
PERMIT_GROUP_CONTENT = 114;
CANCEL_GROUP_CONTENT = 115;
LEAVE_GROUP_CONTENT = 116;
P2P_GROUP_CHANGE_CONTENT = 117;
GROUP_CHANGE_CONTENT = 118;
}
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;
MESSAGETYPE_ANCHORREMINDER = 1;
MESSAGETYPE_ENTERROOMEXPIRESOON = 2;
MESSAGETYPE_SUBGOALCREATETOANCHOR = 3;
MESSAGETYPE_SUBGOALCOMPLETETOAUDIENCE = 4;
MESSAGETYPE_SUBGOALCOMPLETETOANCHOR = 5;
MESSAGETYPE_SUBGIFTTIKTOK2USERNOTICE = 6;
MESSAGETYPE_SUBGIFTTIKTOK2ANCHORNOTICE = 7;
MESSAGETYPE_SUBGIFTTRECEIVESENDNOTICE = 8;
MESSAGETYPE_SUBGIFTSENDSUCCEEDROOMMESSAGE = 9;
MESSAGETYPE_SUBGIFTSENDSUCCEEDANCHORNOTICE = 10;
MESSAGETYPE_SUBGIFTLOWVERSIONUPGRADENOTICE = 11;
MESSAGETYPE_SUBGIFTUSERBUYAUTHNOTICE = 12;
}
enum Scene {
SCENE_UNKNOWN = 0;
SCENE_CO_HOST = 2;
SCENE_MULTI_LIVE = 4;
} }

View File

@@ -18,17 +18,13 @@ message WebcastPushFrame {
uint64 LogId = 2; uint64 LogId = 2;
uint64 Service = 3; uint64 Service = 3;
uint64 Method = 4; uint64 Method = 4;
map<string,string> headers = 5; map<string, string> headers = 5;
string PayloadEncoding = 6; string PayloadEncoding = 6;
string PayloadType = 7; string PayloadType = 7;
bytes Payload = 8; bytes Payload = 8;
} }
message WebcastWebsocketAck {
uint64 Id = 1;
string Type = 2;
}
//@WebcastResponse //@WebcastResponse
@@ -81,6 +77,13 @@ message WebcastGiftMessage {
bool isFirstSent = 25; bool isFirstSent = 25;
string orderId = 28; string orderId = 28;
UserIdentity userIdentity = 32; UserIdentity userIdentity = 32;
UserGiftReciever userGiftReciever = 23;
message UserGiftReciever
{
int64 userId = 1;
string deviceName = 10;
}
message GiftIMPriority { message GiftIMPriority {
repeated int64 queueSizesList = 1; repeated int64 queueSizesList = 1;
@@ -177,6 +180,8 @@ message WebcastCaptionMessage {
} }
} }
// Comment sent by User // Comment sent by User
//@WebcastChatMessage //@WebcastChatMessage
message WebcastChatMessage { message WebcastChatMessage {
@@ -195,7 +200,7 @@ message WebcastChatMessage {
int32 quickChatScene = 16; int32 quickChatScene = 16;
int32 communityFlaggedStatus = 17; int32 communityFlaggedStatus = 17;
UserIdentity UserIdentity = 18; UserIdentity UserIdentity = 18;
map<int32,string> CommentQualityScores = 19; map<int32, string> CommentQualityScores = 19;
// @EmoteWithIndex // @EmoteWithIndex
// proto.webcast.im.ChatMessage // proto.webcast.im.ChatMessage
@@ -275,14 +280,14 @@ message WebcastGoalUpdateMessage {
int64 contributorId = 4; int64 contributorId = 4;
Image contributorAvatar = 5; Image contributorAvatar = 5;
string contributorDisplayId = 6; string contributorDisplayId = 6;
// SubGoal contributeSubgoal = 7; // SubGoal contributeSubgoal = 7;
int64 contributeCount = 9; int64 contributeCount = 9;
int64 contributeScore = 10; int64 contributeScore = 10;
int64 giftRepeatCount = 11; int64 giftRepeatCount = 11;
string contributorIdStr = 12; string contributorIdStr = 12;
bool pin = 13; bool pin = 13;
bool unpin = 14; bool unpin = 14;
// GoalPinInfo pinInfo = 15; // GoalPinInfo pinInfo = 15;
} }
// Message related to Chat-moderation? // Message related to Chat-moderation?
@@ -346,7 +351,7 @@ message WebcastSocialMessage {
message WebcastSubNotifyMessage { message WebcastSubNotifyMessage {
Common common = 1; Common common = 1;
User user = 2; User user = 2;
// ExhibitionType exhibitionType = 3; // Enum // ExhibitionType exhibitionType = 3; // Enum
int64 subMonth = 4; int64 subMonth = 4;
SubscribeType subscribeType = 5; // Enum SubscribeType subscribeType = 5; // Enum
OldSubscribeStatus oldSubscribeStatus = 6; // Enum OldSubscribeStatus oldSubscribeStatus = 6; // Enum
@@ -440,7 +445,7 @@ message WebcastMemberMessage {
//@WebcastPollMessage //@WebcastPollMessage
message WebcastPollMessage { message WebcastPollMessage {
Common common = 1; Common common = 1;
int32 messageType = 2; MessageType messageType = 2;
int64 pollId = 3; int64 pollId = 3;
PollStartContent startContent = 4; PollStartContent startContent = 4;
PollEndContent endContent = 5; PollEndContent endContent = 5;
@@ -505,6 +510,9 @@ message WebcastHourlyRankMessage {
} }
} }
//<Battles>
//@WebcastLinkMicArmies //@WebcastLinkMicArmies
message WebcastLinkMicArmies { message WebcastLinkMicArmies {
Common common = 1; Common common = 1;
@@ -521,6 +529,45 @@ message WebcastLinkMicArmies {
uint32 data4 = 12; uint32 data4 = 12;
uint32 data5 = 13; uint32 data5 = 13;
} }
//@WebcastLinkMicBattlePunishFinish
message WebcastLinkMicBattlePunishFinish {
Common Header = 1;
uint64 Id1 = 2;
uint64 Timestamp = 3;
uint32 Data4 = 4;
uint64 Id2 = 5;
LinkMicBattlePunishFinishData Data6 = 6;
message LinkMicBattlePunishFinishData {
uint64 Id2 = 1; // Same as Id2 in outer object (loser?)
uint64 Timestamp = 2;
uint32 Data3 = 3;
uint64 Id1 = 4; // Same as Id1 in outer object (winner?)
uint32 Data5 = 5;
uint32 Data6 = 6;
uint32 Data8 = 8;
}
}
//@WebcastLinkmicBattleTaskMessage
message WebcastLinkmicBattleTaskMessage {
Common Header = 1;
uint32 Data2 = 2;
LinkmicBattleTaskData Data3 = 3;
LinkmicBattleTaskData2 Data5 = 5;
message LinkmicBattleTaskData {
BattleTaskData Data1 = 1;
}
message BattleTaskData {
uint32 Data1 = 1;
}
message LinkmicBattleTaskData2 {
uint32 Data1 = 1;
uint32 Data2 = 2;
}
}
//@WebcastLinkMicBattle //@WebcastLinkMicBattle
message WebcastLinkMicBattle { message WebcastLinkMicBattle {
@@ -570,11 +617,10 @@ message WebcastLinkMicFanTicketMethod {
Common common = 1; Common common = 1;
FanTicketRoomNoticeContent FanTicketRoomNotice = 2; FanTicketRoomNoticeContent FanTicketRoomNotice = 2;
} }
//@WebcastLinkMicMethod //@WebcastLinkMicMethod
message WebcastLinkMicMethod { message WebcastLinkMicMethod {
Common common = 1; Common common = 1;
int64 messageType = 2; MessageType messageType = 2;
string accessKey = 3; string accessKey = 3;
int64 anchorLinkmicId = 4; int64 anchorLinkmicId = 4;
int64 userId = 5; int64 userId = 5;
@@ -588,6 +634,8 @@ message WebcastLinkMicMethod {
int64 inviteUid = 13; int64 inviteUid = 13;
} }
//<Battles>
//@WebcastLiveIntroMessage //@WebcastLiveIntroMessage
message WebcastLiveIntroMessage { message WebcastLiveIntroMessage {
Common common = 1; Common common = 1;
@@ -630,7 +678,7 @@ message WebcastMsgDetectMessage {
bool detectP2PMsg = 3; bool detectP2PMsg = 3;
bool detectRoomMsg = 4; bool detectRoomMsg = 4;
bool httpOptimize = 5; bool httpOptimize = 5;
} }
} }
//@WebcastOecLiveShoppingMessage //@WebcastOecLiveShoppingMessage
@@ -681,39 +729,63 @@ message WebcastSystemMessage {
//@WebcastLinkMessage //@WebcastLinkMessage
message WebcastLinkMessage { message WebcastLinkMessage {
Common common = 1; Common common = 1;
uint32 data1 = 2; LinkMessageType MessageType = 2;
uint64 data2 = 3; int64 LinkerId = 3;
uint32 data3 = 4; Scene Scene = 4;
LinkMessageData data = 18; LinkerInviteContent InviteContent = 5;
LinkMessageUserContainer user = 20; LinkerReplyContent ReplyContent = 6;
string token = 200; LinkerCreateContent CreateContent = 7;
LinkerCloseContent CloseContent = 8;
message LinkMessageData { LinkerEnterContent EnterContent = 9;
DataContainer data = 1; // index 1 is an Id LinkerLeaveContent LeaveContent = 10;
} LinkerCancelContent CancelContent = 11;
LinkerKickOutContent KickOutContent = 12;
message LinkMessageUserContainer { LinkerLinkedListChangeContent LinkedListChangeContent = 13;
LinkMessageUser user = 1; LinkerUpdateUserContent UpdateUserContent = 14;
repeated LinkMessageUser otherUsers = 2; LinkerWaitingListChangeContent WaitingListChangeContent = 15;
LinkerMuteContent MuteContent = 16;
message LinkMessageUser { LinkerRandomMatchContent RandomMatchContent = 17;
User user = 1; LinkerUpdateUserSettingContent UpdateUserSettingContent = 18;
uint64 timeStamp = 2; LinkerMicIdxUpdateContent MicIdxUpdateContent = 19;
uint32 data1 = 4; LinkerListChangeContent ListChangeContent = 20;
string idString = 5; CohostListChangeContent CohostListChangeContent = 21;
uint32 data2 = 7; LinkerMediaChangeContent MediaChangeContent = 22;
} LinkerAcceptNoticeContent ReplyAcceptNoticeContent = 23;
} LinkerSysKickOutContent SysKickOutContent = 101;
LinkmicUserToastContent UserToastContent = 102;
string extra = 200;
int64 expireTimestamp = 201;
string transferExtra = 202;
} }
//@WebcastLinkLayerMessage
// @WebcastLinkLayerMessage
message WebcastLinkLayerMessage { message WebcastLinkLayerMessage {
Common common = 1; Common common = 1;
LinkLayerMessageType messageType = 2; MessageType messageType = 2; // Enum
int64 channelId = 3; int64 channelId = 3;
Scene scene = 4; // Enum
CreateChannelContent createChannelContent = 100;
ListChangeContent listChangeContent = 102;
InviteContent inviteContent = 103;
ApplyContent applyContent = 104;
PermitApplyContent permitApplyContent = 105;
ReplyInviteContent replyInviteContent = 106;
KickOutContent kickOutContent = 107;
CancelApplyContent cancelApplyContent = 108;
CancelInviteContent cancelInviteContent = 109;
LeaveContent leaveContent = 110;
FinishChannelContent finishContent = 111;
JoinDirectContent joinDirectContent = 112;
JoinGroupContent joinGroupContent = 113;
PermitJoinGroupContent permitGroupContent = 114;
CancelJoinGroupContent cancelGroupContent = 115;
LeaveJoinGroupContent leaveGroupContent = 116;
P2PGroupChangeContent p2pGroupChangeContent = 117;
BusinessContent businessContent = 200;
} }
// @RoomVerifyMessage
message RoomVerifyMessage { message RoomVerifyMessage {
Common common = 1; Common common = 1;
int32 action = 2; int32 action = 2;

View File

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

View File

@@ -23,68 +23,30 @@
package io.github.jwdeveloper.tiktok; 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 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 * @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 * @return LiveClientBuilder
*/ */
public static LiveClientBuilder newClient(String hostName) public static LiveClientBuilder newClient(String hostName) {
{
return new TikTokLiveClientBuilder(hostName); return new TikTokLiveClientBuilder(hostName);
} }
/** /**
* Use to get some data from TikTok about users are lives
* *
* @param hostName profile name of Tiktok user could be found in profile link * @return LiveHttpClient
* example: https://www.tiktok.com/@dostawcavideo hostName would be dostawcavideo
* @return true if live is Online, false if is offline
*/ */
public static boolean isLiveOnline(String hostName) public static LiveHttpClient requests() {
{
return new TikTokDataChecker().isOnline(hostName);
}
/** return new TikTokLiveHttpClient();
*
* @param hostName profile name of Tiktok user could be found in profile link
* example: https://www.tiktok.com/@dostawcavideo hostName would be dostawcavideo
* @return true if live is Online, false if is offline
*/
public static CompletableFuture<Boolean> isLiveOnlineAsync(String hostName)
{
return new TikTokDataChecker().isOnlineAsync(hostName);
} }
/**
*
* @param hostName profile name of Tiktok user could be found in profile link
* example: https://www.tiktok.com/@dostawcavideo hostName would be dostawcavideo
* @return true is hostName name is valid and exists, false if not
*/
public static boolean isHostNameValid(String hostName)
{
return new TikTokDataChecker().isHostNameValid(hostName);
}
/**
*
* @param hostName profile name of Tiktok user could be found in profile link
* example: https://www.tiktok.com/@dostawcavideo hostName would be dostawcavideo
* @return true is hostName name is valid and exists, false if not
*/
public static CompletableFuture<Boolean> isHostNameValidAsync(String hostName)
{
return new TikTokDataChecker().isHostNameValidAsync(hostName);
}
} }

View File

@@ -25,18 +25,21 @@ package io.github.jwdeveloper.tiktok;
import io.github.jwdeveloper.tiktok.data.events.TikTokDisconnectedEvent; import io.github.jwdeveloper.tiktok.data.events.TikTokDisconnectedEvent;
import io.github.jwdeveloper.tiktok.data.events.TikTokErrorEvent; import io.github.jwdeveloper.tiktok.data.events.TikTokErrorEvent;
import io.github.jwdeveloper.tiktok.data.events.TikTokReconnectingEvent; import io.github.jwdeveloper.tiktok.data.events.TikTokReconnectingEvent;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent;
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.TikTokLiveException;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveOfflineHostException; import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveOfflineHostException;
import io.github.jwdeveloper.tiktok.gifts.TikTokGiftManager; 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.ListenersManager;
import io.github.jwdeveloper.tiktok.listener.TikTokListenersManager; import io.github.jwdeveloper.tiktok.listener.TikTokListenersManager;
import io.github.jwdeveloper.tiktok.live.GiftManager; import io.github.jwdeveloper.tiktok.live.GiftManager;
import io.github.jwdeveloper.tiktok.live.LiveClient; import io.github.jwdeveloper.tiktok.live.LiveClient;
import io.github.jwdeveloper.tiktok.live.LiveRoomInfo; 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.models.ConnectionState;
import io.github.jwdeveloper.tiktok.data.settings.LiveClientSettings;
import io.github.jwdeveloper.tiktok.websocket.SocketClient; import io.github.jwdeveloper.tiktok.websocket.SocketClient;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
@@ -46,24 +49,24 @@ import java.util.logging.Logger;
public class TikTokLiveClient implements LiveClient { public class TikTokLiveClient implements LiveClient {
private final TikTokRoomInfo liveRoomInfo; private final TikTokRoomInfo liveRoomInfo;
private final TikTokGiftManager tikTokGiftManager; private final TikTokGiftManager tikTokGiftManager;
private final TikTokApiService apiService; private final TikTokLiveHttpClient httpClient;
private final SocketClient webSocketClient; private final SocketClient webSocketClient;
private final TikTokEventObserver tikTokEventHandler; private final TikTokLiveEventHandler tikTokEventHandler;
private final ClientSettings clientSettings; private final LiveClientSettings clientSettings;
private final TikTokListenersManager listenersManager; private final TikTokListenersManager listenersManager;
private final Logger logger; private final Logger logger;
public TikTokLiveClient(TikTokRoomInfo tikTokLiveMeta, public TikTokLiveClient(TikTokRoomInfo tikTokLiveMeta,
TikTokApiService tikTokApiService, TikTokLiveHttpClient tiktokHttpClient,
SocketClient webSocketClient, SocketClient webSocketClient,
TikTokGiftManager tikTokGiftManager, TikTokGiftManager tikTokGiftManager,
TikTokEventObserver tikTokEventHandler, TikTokLiveEventHandler tikTokEventHandler,
ClientSettings clientSettings, LiveClientSettings clientSettings,
TikTokListenersManager listenersManager, TikTokListenersManager listenersManager,
Logger logger) { Logger logger) {
this.liveRoomInfo = tikTokLiveMeta; this.liveRoomInfo = tikTokLiveMeta;
this.tikTokGiftManager = tikTokGiftManager; this.tikTokGiftManager = tikTokGiftManager;
this.apiService = tikTokApiService; this.httpClient = tiktokHttpClient;
this.webSocketClient = webSocketClient; this.webSocketClient = webSocketClient;
this.tikTokEventHandler = tikTokEventHandler; this.tikTokEventHandler = tikTokEventHandler;
this.clientSettings = clientSettings; this.clientSettings = clientSettings;
@@ -79,8 +82,10 @@ public class TikTokLiveClient implements LiveClient {
onConnection.accept(this); onConnection.accept(this);
return this; return this;
}); });
} }
public CompletableFuture<LiveClient> connectAsync() { public CompletableFuture<LiveClient> connectAsync() {
return CompletableFuture.supplyAsync(() -> return CompletableFuture.supplyAsync(() ->
{ {
@@ -92,8 +97,7 @@ public class TikTokLiveClient implements LiveClient {
public void connect() { public void connect() {
try { try {
tryConnect(); tryConnect();
} catch (TikTokLiveException e) } catch (TikTokLiveException e) {
{
setState(ConnectionState.DISCONNECTED); setState(ConnectionState.DISCONNECTED);
tikTokEventHandler.publish(this, new TikTokErrorEvent(e)); tikTokEventHandler.publish(this, new TikTokErrorEvent(e));
tikTokEventHandler.publish(this, new TikTokDisconnectedEvent()); tikTokEventHandler.publish(this, new TikTokDisconnectedEvent());
@@ -108,9 +112,58 @@ public class TikTokLiveClient implements LiveClient {
this.connect(); this.connect();
} }
throw e; throw e;
} catch (Exception e) {
logger.info("Unhandled exception report this bug to github https://github.com/jwdeveloper/TikTokLiveJava/issues");
this.disconnect();
e.printStackTrace();
} }
} }
public void tryConnect() {
if (!liveRoomInfo.hasConnectionState(ConnectionState.DISCONNECTED))
{
throw new TikTokLiveException("Already connected");
}
setState(ConnectionState.CONNECTING);
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 liveDataRequest = new LiveData.Request(userData.getRoomId());
var liveData = httpClient.fetchLiveData(liveDataRequest);
if (liveData.getLiveStatus() == LiveData.LiveStatus.HostNotFound) {
throw new TikTokLiveOfflineHostException("LiveStream for Host name could not be found.");
}
if (liveData.getLiveStatus() == LiveData.LiveStatus.HostOffline) {
throw new TikTokLiveOfflineHostException("LiveStream for not be found, is the Host offline?");
}
liveRoomInfo.setTitle(liveData.getTitle());
liveRoomInfo.setViewersCount(liveData.getViewers());
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);
setState(ConnectionState.CONNECTED);
tikTokEventHandler.publish(this, new TikTokRoomInfoEvent(liveRoomInfo));
}
public void disconnect() { public void disconnect() {
if (liveRoomInfo.hasConnectionState(ConnectionState.DISCONNECTED)) { if (liveRoomInfo.hasConnectionState(ConnectionState.DISCONNECTED)) {
return; return;
@@ -119,33 +172,13 @@ public class TikTokLiveClient implements LiveClient {
setState(ConnectionState.DISCONNECTED); setState(ConnectionState.DISCONNECTED);
} }
public void tryConnect() { private void setState(ConnectionState connectionState) {
if (liveRoomInfo.hasConnectionState(ConnectionState.CONNECTED)) logger.info("TikTokLive client state: " + connectionState.name());
throw new TikTokLiveException("Already connected"); liveRoomInfo.setConnectionState(connectionState);
if (liveRoomInfo.hasConnectionState(ConnectionState.CONNECTING)) }
throw new TikTokLiveException("Already connecting");
setState(ConnectionState.CONNECTING); public void publishEvent(TikTokEvent event) {
tikTokEventHandler.publish(this, event);
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);
}
var roomData = apiService.fetchRoomInfo();
if (roomData.getStatus() != LiveRoomMeta.LiveRoomStatus.HostOnline) {
throw new TikTokLiveOfflineHostException("LiveStream for Host name could not be found. Is the Host online?");
}
var clientData = apiService.fetchClientData();
webSocketClient.start(clientData, this);
setState(ConnectionState.CONNECTED);
} }
@@ -169,9 +202,4 @@ public class TikTokLiveClient implements LiveClient {
} }
private void setState(ConnectionState connectionState) { }
logger.info("TikTokLive client state: " + connectionState.name());
liveRoomInfo.setConnectionState(connectionState);
}
}

View File

@@ -27,10 +27,10 @@ import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent;
import io.github.jwdeveloper.tiktok.data.events.envelop.TikTokChestEvent; import io.github.jwdeveloper.tiktok.data.events.envelop.TikTokChestEvent;
import io.github.jwdeveloper.tiktok.data.events.gift.TikTokGiftComboEvent; import io.github.jwdeveloper.tiktok.data.events.gift.TikTokGiftComboEvent;
import io.github.jwdeveloper.tiktok.data.events.gift.TikTokGiftEvent; import io.github.jwdeveloper.tiktok.data.events.gift.TikTokGiftEvent;
import io.github.jwdeveloper.tiktok.data.events.http.TikTokHttpResponseEvent;
import io.github.jwdeveloper.tiktok.data.events.poll.TikTokPollEvent; import io.github.jwdeveloper.tiktok.data.events.poll.TikTokPollEvent;
import io.github.jwdeveloper.tiktok.data.events.room.TikTokRoomEvent; import io.github.jwdeveloper.tiktok.data.events.room.TikTokRoomInfoEvent;
import io.github.jwdeveloper.tiktok.data.events.room.TikTokRoomPinEvent; import io.github.jwdeveloper.tiktok.data.events.room.TikTokRoomPinEvent;
import io.github.jwdeveloper.tiktok.data.events.room.TikTokRoomUserInfoEvent;
import io.github.jwdeveloper.tiktok.data.events.social.TikTokFollowEvent; import io.github.jwdeveloper.tiktok.data.events.social.TikTokFollowEvent;
import io.github.jwdeveloper.tiktok.data.events.social.TikTokJoinEvent; import io.github.jwdeveloper.tiktok.data.events.social.TikTokJoinEvent;
import io.github.jwdeveloper.tiktok.data.events.social.TikTokLikeEvent; import io.github.jwdeveloper.tiktok.data.events.social.TikTokLikeEvent;
@@ -40,23 +40,27 @@ import io.github.jwdeveloper.tiktok.data.events.websocket.TikTokWebsocketRespons
import io.github.jwdeveloper.tiktok.data.events.websocket.TikTokWebsocketUnhandledMessageEvent; import io.github.jwdeveloper.tiktok.data.events.websocket.TikTokWebsocketUnhandledMessageEvent;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveException; import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveException;
import io.github.jwdeveloper.tiktok.gifts.TikTokGiftManager; import io.github.jwdeveloper.tiktok.gifts.TikTokGiftManager;
import io.github.jwdeveloper.tiktok.handlers.TikTokEventObserver; import io.github.jwdeveloper.tiktok.http.HttpClientFactory;
import io.github.jwdeveloper.tiktok.handlers.TikTokMessageHandlerRegistration;
import io.github.jwdeveloper.tiktok.handlers.events.TikTokGiftEventHandler;
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.listener.TikTokEventListener; import io.github.jwdeveloper.tiktok.listener.TikTokEventListener;
import io.github.jwdeveloper.tiktok.listener.TikTokListenersManager; 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.LiveClient;
import io.github.jwdeveloper.tiktok.live.builder.EventConsumer; import io.github.jwdeveloper.tiktok.live.builder.EventConsumer;
import io.github.jwdeveloper.tiktok.live.builder.LiveClientBuilder; import io.github.jwdeveloper.tiktok.live.builder.LiveClientBuilder;
import io.github.jwdeveloper.tiktok.mappers.TikTokGenericEventMapper; import io.github.jwdeveloper.tiktok.mappers.TikTokGenericEventMapper;
import io.github.jwdeveloper.tiktok.mappers.TikTokLiveMapper;
import io.github.jwdeveloper.tiktok.mappers.TikTokLiveMapperHelper;
import io.github.jwdeveloper.tiktok.mappers.TikTokMapper;
import io.github.jwdeveloper.tiktok.mappers.data.MappingResult;
import io.github.jwdeveloper.tiktok.mappers.handlers.TikTokCommonEventHandler;
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.utils.ConsoleColors;
import io.github.jwdeveloper.tiktok.websocket.TikTokWebSocketClient; import io.github.jwdeveloper.tiktok.websocket.TikTokWebSocketClient;
import java.time.Duration;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
@@ -65,21 +69,31 @@ import java.util.logging.*;
public class TikTokLiveClientBuilder implements LiveClientBuilder { public class TikTokLiveClientBuilder implements LiveClientBuilder {
protected final ClientSettings clientSettings; protected final LiveClientSettings clientSettings;
protected final Logger logger; protected final Logger logger;
protected final TikTokEventObserver tikTokEventHandler; protected final TikTokLiveEventHandler tikTokEventHandler;
protected final List<TikTokEventListener> listeners; protected final List<TikTokEventListener> listeners;
protected Consumer<TikTokMapper> onCustomMappings;
public TikTokLiveClientBuilder(String userName) { public TikTokLiveClientBuilder(String userName)
this.tikTokEventHandler = new TikTokEventObserver(); {
this.clientSettings = Constants.DefaultClientSettings(); this.clientSettings = LiveClientSettings.createDefault();
this.clientSettings.setHostName(userName); this.clientSettings.setHostName(userName);
this.tikTokEventHandler = new TikTokLiveEventHandler();
this.logger = Logger.getLogger(TikTokLive.class.getSimpleName() + " " + userName); this.logger = Logger.getLogger(TikTokLive.class.getSimpleName() + " " + userName);
this.listeners = new ArrayList<>(); this.listeners = new ArrayList<>();
this.onCustomMappings = (e) -> {
};
} }
public TikTokLiveClientBuilder configure(Consumer<ClientSettings> consumer) { public LiveClientBuilder onMapping(Consumer<TikTokMapper> onCustomMappings) {
consumer.accept(clientSettings); this.onCustomMappings = onCustomMappings;
return this;
}
public TikTokLiveClientBuilder configure(Consumer<LiveClientSettings> onConfigure) {
onConfigure.accept(clientSettings);
return this; return this;
} }
@@ -89,29 +103,23 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder {
} }
protected void validate() { protected void validate() {
if (clientSettings.getClientLanguage() == null || clientSettings.getClientLanguage().isEmpty()) {
if (clientSettings.getTimeout() == null) { clientSettings.setClientLanguage("en");
clientSettings.setTimeout(Duration.ofSeconds(Constants.DEFAULT_TIMEOUT));
}
if (clientSettings.getClientLanguage() == null || clientSettings.getClientLanguage().equals("")) {
clientSettings.setClientLanguage(Constants.DefaultClientSettings().getClientLanguage());
} }
if (clientSettings.getHostName() == null || clientSettings.getHostName().equals("")) { if (clientSettings.getHostName() == null || clientSettings.getHostName().isEmpty()) {
throw new TikTokLiveException("HostName can not be null"); throw new TikTokLiveException("HostName can not be null");
} }
if (clientSettings.getHostName().startsWith("@")) if (clientSettings.getHostName().startsWith("@")) {
{
clientSettings.setHostName(clientSettings.getHostName().substring(1)); clientSettings.setHostName(clientSettings.getHostName().substring(1));
} }
var params = clientSettings.getClientParameters(); var httpSettings = clientSettings.getHttpSettings();
params.put("app_language", clientSettings.getClientLanguage()); httpSettings.getParams().put("app_language", clientSettings.getClientLanguage());
params.put("webcast_language", clientSettings.getClientLanguage()); httpSettings.getParams().put("webcast_language", clientSettings.getClientLanguage());
var handler = new ConsoleHandler(); var handler = new ConsoleHandler();
@@ -128,15 +136,10 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder {
}); });
logger.setUseParentHandlers(false); logger.setUseParentHandlers(false);
logger.addHandler(handler); logger.addHandler(handler);
logger.setLevel(clientSettings.getLogLevel()); logger.setLevel(clientSettings.getLogLevel());
if (!clientSettings.isPrintToConsole()) {
if (clientSettings.isPrintToConsole() && clientSettings.getLogLevel() == Level.OFF) { logger.setLevel(Level.OFF);
logger.setLevel(Level.ALL);
} }
} }
public LiveClient build() { public LiveClient build() {
@@ -146,28 +149,22 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder {
tiktokRoomInfo.setHostName(clientSettings.getHostName()); tiktokRoomInfo.setHostName(clientSettings.getHostName());
var listenerManager = new TikTokListenersManager(listeners, tikTokEventHandler); var listenerManager = new TikTokListenersManager(listeners, tikTokEventHandler);
var cookieJar = new TikTokCookieJar();
var requestFactory = new TikTokHttpRequestFactory(cookieJar);
var apiClient = new TikTokHttpClient(cookieJar, requestFactory);
var apiService = new TikTokApiService(apiClient, logger, clientSettings);
var giftManager = new TikTokGiftManager(logger); var giftManager = new TikTokGiftManager(logger);
var eventMapper = new TikTokGenericEventMapper(); var eventsMapper = createMapper(giftManager, tiktokRoomInfo);
var giftHandler = new TikTokGiftEventHandler(giftManager); var messageHandler = new TikTokLiveMessageHandler(tikTokEventHandler, eventsMapper);
var webResponseHandler = new TikTokMessageHandlerRegistration(tikTokEventHandler,
tiktokRoomInfo,
eventMapper,
giftHandler
);
var webSocketClient = new TikTokWebSocketClient(logger, var httpClientFactory = new HttpClientFactory(clientSettings);
cookieJar, var tikTokLiveHttpClient = new TikTokLiveHttpClient(httpClientFactory);
var webSocketClient = new TikTokWebSocketClient(
clientSettings, clientSettings,
webResponseHandler, messageHandler,
tikTokEventHandler); tikTokEventHandler);
return new TikTokLiveClient(tiktokRoomInfo, return new TikTokLiveClient(tiktokRoomInfo,
apiService, tikTokLiveHttpClient,
webSocketClient, webSocketClient,
giftManager, giftManager,
tikTokEventHandler, tikTokEventHandler,
@@ -176,6 +173,98 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder {
logger); logger);
} }
public TikTokLiveMapper createMapper(GiftManager giftManager, TikTokRoomInfo roomInfo) {
/*
//
*/
var eventMapper = new TikTokGenericEventMapper();
var mapper = new TikTokLiveMapper(new TikTokLiveMapperHelper(eventMapper));
//ConnectionEvents events
var commonHandler = new TikTokCommonEventHandler();
var giftHandler = new TikTokGiftEventHandler(giftManager, roomInfo);
var roomInfoHandler = new TikTokRoomInfoEventHandler(roomInfo);
var socialHandler = new TikTokSocialMediaEventHandler(roomInfo);
mapper.forMessage(WebcastControlMessage.class, commonHandler::handleWebcastControlMessage);
//Room status events
mapper.forMessage(WebcastLiveIntroMessage.class, roomInfoHandler::handleIntro);
mapper.forMessage(WebcastRoomUserSeqMessage.class, roomInfoHandler::handleUserRanking);
mapper.forMessage(WebcastCaptionMessage.class, (inputBytes, messageName, mapperHelper) ->
{
var messageObject = mapperHelper.bytesToWebcastObject(inputBytes, WebcastCaptionMessage.class);
return MappingResult.of(messageObject, new TikTokCaptionEvent(messageObject));
});
//User Interactions events
mapper.forMessage(WebcastChatMessage.class, (inputBytes, messageName, mapperHelper) ->
{
var messageObject = mapperHelper.bytesToWebcastObject(inputBytes, WebcastChatMessage.class);
return MappingResult.of(messageObject, new TikTokCommentEvent(messageObject));
});
mapper.forMessage(WebcastSubNotifyMessage.class, (inputBytes, messageName, mapperHelper) ->
{
var messageObject = mapperHelper.bytesToWebcastObject(inputBytes, WebcastSubNotifyMessage.class);
return MappingResult.of(messageObject, new TikTokSubscribeEvent(messageObject));
});
mapper.forMessage(WebcastEmoteChatMessage.class, (inputBytes, messageName, mapperHelper) ->
{
var messageObject = mapperHelper.bytesToWebcastObject(inputBytes, WebcastEmoteChatMessage.class);
return MappingResult.of(messageObject, new TikTokEmoteEvent(messageObject));
});
mapper.forMessage(WebcastQuestionNewMessage.class, (inputBytes, messageName, mapperHelper) ->
{
var messageObject = mapperHelper.bytesToWebcastObject(inputBytes, WebcastQuestionNewMessage.class);
return MappingResult.of(messageObject, new TikTokQuestionEvent(messageObject));
});
mapper.forMessage(WebcastLikeMessage.class, roomInfoHandler::handleLike);
mapper.forMessage(WebcastGiftMessage.class, giftHandler::handleGifts);
mapper.forMessage(WebcastSocialMessage.class, socialHandler::handle);
mapper.forMessage(WebcastMemberMessage.class, roomInfoHandler::handleMemberMessage);
//Host Interaction events
mapper.forMessage(WebcastPollMessage.class, commonHandler::handlePollEvent);
mapper.forMessage(WebcastRoomPinMessage.class, commonHandler::handlePinMessage);
mapper.forMessage(WebcastChatMessage.class, (inputBytes, messageName, mapperHelper) ->
{
var messageObject = mapperHelper.bytesToWebcastObject(inputBytes, WebcastChatMessage.class);
return MappingResult.of(messageObject, new TikTokCommentEvent(messageObject));
});
//LinkMic events
// mapper.webcastObjectToConstructor(WebcastLinkMicBattle.class, TikTokLinkMicBattleEvent.class);
// mapper.webcastObjectToConstructor(WebcastLinkMicArmies.class, TikTokLinkMicArmiesEvent.class);
// mapper.webcastObjectToConstructor(WebcastLinkMicMethod.class, TikTokLinkMicMethodEvent.class);
// mapper.webcastObjectToConstructor(WebcastLinkMicFanTicketMethod.class, TikTokLinkMicFanTicketEvent.class);
//Rank events
// mapper.webcastObjectToConstructor(WebcastRankTextMessage.class, TikTokRankTextEvent.class);
// mapper.webcastObjectToConstructor(WebcastRankUpdateMessage.class, TikTokRankUpdateEvent.class);
// mapper.webcastObjectToConstructor(WebcastHourlyRankMessage.class, TikTokRankUpdateEvent.class);
//Others events
// mapper.webcastObjectToConstructor(WebcastInRoomBannerMessage.class, TikTokInRoomBannerEvent.class);
// 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(WebcastImDeleteMessage.class, TikTokIMDeleteEvent.class);
// mapper.bytesToEvents(WebcastEnvelopeMessage.class, commonHandler::handleEnvelop);
onCustomMappings.accept(mapper);
return mapper;
}
public LiveClient buildAndConnect() { public LiveClient buildAndConnect() {
var client = build(); var client = build();
client.connect(); client.connect();
@@ -186,18 +275,20 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder {
return build().connectAsync(); return build().connectAsync();
} }
public TikTokLiveClientBuilder onUnhandledSocial( public TikTokLiveClientBuilder onUnhandledSocial(
EventConsumer<TikTokUnhandledSocialEvent> event) { EventConsumer<TikTokUnhandledSocialEvent> event) {
tikTokEventHandler.subscribe(TikTokUnhandledSocialEvent.class, event); tikTokEventHandler.subscribe(TikTokUnhandledSocialEvent.class, event);
return this; return this;
} }
// @Override // @Override
public LiveClientBuilder onChest(EventConsumer<TikTokChestEvent> event) { public LiveClientBuilder onChest(EventConsumer<TikTokChestEvent> event) {
tikTokEventHandler.subscribe(TikTokChestEvent.class, event); tikTokEventHandler.subscribe(TikTokChestEvent.class, event);
return this; return this;
} }
public TikTokLiveClientBuilder onLinkMicFanTicket( public TikTokLiveClientBuilder onLinkMicFanTicket(
EventConsumer<TikTokLinkMicFanTicketEvent> event) { EventConsumer<TikTokLinkMicFanTicketEvent> event) {
tikTokEventHandler.subscribe(TikTokLinkMicFanTicketEvent.class, event); tikTokEventHandler.subscribe(TikTokLinkMicFanTicketEvent.class, event);
@@ -247,16 +338,31 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder {
return this; return this;
} }
public TikTokLiveClientBuilder onRoom(EventConsumer<TikTokRoomEvent> event) { @Override
tikTokEventHandler.subscribe(TikTokRoomEvent.class, event); public <E extends TikTokEvent> LiveClientBuilder onEvent(Class<E> eventClass, EventConsumer<E> event) {
tikTokEventHandler.subscribe(eventClass, event);
return this; return this;
} }
@Override
public LiveClientBuilder onRoomInfo(EventConsumer<TikTokRoomInfoEvent> event) {
tikTokEventHandler.subscribe(TikTokRoomInfoEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onLivePaused(EventConsumer<TikTokLivePausedEvent> event) { public TikTokLiveClientBuilder onLivePaused(EventConsumer<TikTokLivePausedEvent> event) {
tikTokEventHandler.subscribe(TikTokLivePausedEvent.class, event); tikTokEventHandler.subscribe(TikTokLivePausedEvent.class, event);
return this; return this;
} }
@Override
public LiveClientBuilder onLiveUnpaused(EventConsumer<TikTokLiveUnpausedEvent> event) {
tikTokEventHandler.subscribe(TikTokLiveUnpausedEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onLike(EventConsumer<TikTokLikeEvent> event) { public TikTokLiveClientBuilder onLike(EventConsumer<TikTokLikeEvent> event) {
tikTokEventHandler.subscribe(TikTokLikeEvent.class, event); tikTokEventHandler.subscribe(TikTokLikeEvent.class, event);
return this; return this;
@@ -329,15 +435,14 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder {
return this; return this;
} }
public TikTokLiveClientBuilder onRoomUserInfo( public TikTokLiveClientBuilder onComment(EventConsumer<TikTokCommentEvent> event) {
EventConsumer<TikTokRoomUserInfoEvent> event) { tikTokEventHandler.subscribe(TikTokCommentEvent.class, event);
tikTokEventHandler.subscribe(TikTokRoomUserInfoEvent.class, event);
return this; return this;
} }
@Override
public TikTokLiveClientBuilder onComment(EventConsumer<TikTokCommentEvent> event) { public LiveClientBuilder onHttpResponse(EventConsumer<TikTokHttpResponseEvent> action) {
tikTokEventHandler.subscribe(TikTokCommentEvent.class, event); tikTokEventHandler.subscribe(TikTokHttpResponseEvent.class, action);
return this; return this;
} }
@@ -439,11 +544,4 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder {
tikTokEventHandler.subscribe(TikTokReconnectingEvent.class, event); tikTokEventHandler.subscribe(TikTokReconnectingEvent.class, event);
return this; return this;
} }
} }

View File

@@ -20,7 +20,7 @@
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * 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.data.events.common.TikTokEvent;
import io.github.jwdeveloper.tiktok.live.builder.EventConsumer; import io.github.jwdeveloper.tiktok.live.builder.EventConsumer;
@@ -31,10 +31,10 @@ import java.util.HashSet;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
public class TikTokEventObserver { public class TikTokLiveEventHandler {
private final Map<Class<?>, Set<EventConsumer>> events; private final Map<Class<?>, Set<EventConsumer>> events;
public TikTokEventObserver() { public TikTokLiveEventHandler() {
events = new HashMap<>(); events = new HashMap<>();
} }

View File

@@ -0,0 +1,197 @@
/*
* 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.LiveClientSettings;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveRequestException;
import io.github.jwdeveloper.tiktok.exceptions.TikTokSignServerException;
import io.github.jwdeveloper.tiktok.http.HttpClientFactory;
import io.github.jwdeveloper.tiktok.http.LiveHttpClient;
import io.github.jwdeveloper.tiktok.http.mappers.GiftsDataMapper;
import io.github.jwdeveloper.tiktok.http.mappers.LiveDataMapper;
import io.github.jwdeveloper.tiktok.http.mappers.LiveUserDataMapper;
import io.github.jwdeveloper.tiktok.http.mappers.SignServerResponseMapper;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastResponse;
import java.net.http.HttpResponse;
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 LiveUserDataMapper liveUserDataMapper;
private final LiveDataMapper liveDataMapper;
private final SignServerResponseMapper singServerResponseMapper;
private final GiftsDataMapper giftsDataMapper;
public TikTokLiveHttpClient(HttpClientFactory factory) {
this.httpFactory = factory;
liveUserDataMapper = new LiveUserDataMapper();
liveDataMapper = new LiveDataMapper();
singServerResponseMapper = new SignServerResponseMapper();
giftsDataMapper = new GiftsDataMapper();
}
public TikTokLiveHttpClient() {
this(new HttpClientFactory(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 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 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) {
var signServerResponse = getSignedUrl(request.getRoomId());
var credentialsResponse = getWebsocketCredentialsResponse(signServerResponse.getSignedUrl());
try {
var optionalHeader = credentialsResponse.headers().firstValue("set-cookie");
if (optionalHeader.isEmpty()) {
throw new TikTokSignServerException("Sign server does not returned 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 singServerResponseMapper.map(json);
}
HttpResponse<byte[]> getWebsocketCredentialsResponse(String signedUrl) {
var optionalResponse = httpFactory
.clientEmpty(signedUrl)
.build()
.toResponse(HttpResponse.BodyHandlers.ofByteArray());
if (optionalResponse.isEmpty()) {
throw new TikTokSignServerException("Unable to get websocket connection credentials");
}
return optionalResponse.get();
}
}

View File

@@ -20,51 +20,32 @@
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * 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;
import io.github.jwdeveloper.tiktok.data.events.TikTokErrorEvent; import io.github.jwdeveloper.tiktok.data.events.TikTokErrorEvent;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent;
import io.github.jwdeveloper.tiktok.data.events.websocket.TikTokWebsocketMessageEvent; import io.github.jwdeveloper.tiktok.data.events.websocket.TikTokWebsocketMessageEvent;
import io.github.jwdeveloper.tiktok.data.events.websocket.TikTokWebsocketResponseEvent; import io.github.jwdeveloper.tiktok.data.events.websocket.TikTokWebsocketResponseEvent;
import io.github.jwdeveloper.tiktok.data.events.websocket.TikTokWebsocketUnhandledMessageEvent; import io.github.jwdeveloper.tiktok.data.events.websocket.TikTokWebsocketUnhandledMessageEvent;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveMessageException; import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveMessageException;
import io.github.jwdeveloper.tiktok.live.LiveClient; import io.github.jwdeveloper.tiktok.live.LiveClient;
import io.github.jwdeveloper.tiktok.mappers.TikTokGenericEventMapper; import io.github.jwdeveloper.tiktok.mappers.TikTokLiveMapper;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastResponse; import io.github.jwdeveloper.tiktok.messages.webcast.WebcastResponse;
import io.github.jwdeveloper.tiktok.utils.Stopwatch; import io.github.jwdeveloper.tiktok.utils.Stopwatch;
import java.time.Duration; import java.time.Duration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
public class TikTokLiveMessageHandler {
public abstract class TikTokMessageHandler { private final TikTokLiveEventHandler tikTokEventHandler;
private final TikTokLiveMapper mapper;
private final Map<String, io.github.jwdeveloper.tiktok.handler.TikTokMessageHandler> handlers; public TikTokLiveMessageHandler(TikTokLiveEventHandler tikTokEventHandler, TikTokLiveMapper mapper) {
private final TikTokEventObserver tikTokEventHandler;
protected final TikTokGenericEventMapper mapper;
public TikTokMessageHandler(TikTokEventObserver tikTokEventHandler, TikTokGenericEventMapper mapper) {
handlers = new HashMap<>();
this.tikTokEventHandler = tikTokEventHandler; this.tikTokEventHandler = tikTokEventHandler;
this.mapper = mapper; this.mapper = mapper;
} }
public void registerMapping(Class<?> clazz, Function<byte[], TikTokEvent> func) {
handlers.put(clazz.getSimpleName(), messagePayload -> List.of(func.apply(messagePayload)));
}
public void registerMappings(Class<?> clazz, Function<byte[], List<TikTokEvent>> func) {
handlers.put(clazz.getSimpleName(), func::apply);
}
public void registerMapping(Class<?> input, Class<?> output) {
registerMapping(input, (e) -> mapper.mapToEvent(input, output, e));
}
public void handle(LiveClient client, WebcastResponse webcastResponse) { public void handle(LiveClient client, WebcastResponse webcastResponse) {
tikTokEventHandler.publish(client, new TikTokWebsocketResponseEvent(webcastResponse)); tikTokEventHandler.publish(client, new TikTokWebsocketResponseEvent(webcastResponse));
for (var message : webcastResponse.getMessagesList()) { for (var message : webcastResponse.getMessagesList()) {
@@ -77,21 +58,21 @@ public abstract class TikTokMessageHandler {
} }
} }
public void handleSingleMessage(LiveClient client, WebcastResponse.Message message) throws Exception { public void handleSingleMessage(LiveClient client, WebcastResponse.Message message)
{
var messageClassName = message.getMethod(); var messageClassName = message.getMethod();
if (!handlers.containsKey(messageClassName)) { if (!mapper.isRegistered(messageClassName)) {
tikTokEventHandler.publish(client, new TikTokWebsocketUnhandledMessageEvent(message)); tikTokEventHandler.publish(client, new TikTokWebsocketUnhandledMessageEvent(message));
return; return;
} }
var handler = handlers.get(messageClassName);
var stopwatch = new Stopwatch(); var stopwatch = new Stopwatch();
stopwatch.start(); stopwatch.start();
var events = handler.handle(message.getPayload().toByteArray()); var events = mapper.handleMapping(messageClassName, message.getPayload().toByteArray());
var handlingTimeInMs = stopwatch.stop(); var handlingTimeInMs = stopwatch.stop();
var metadata = new TikTokWebsocketMessageEvent.MetaData(Duration.ofNanos(handlingTimeInMs)); var metadata = new MessageMetaData(Duration.ofNanos(handlingTimeInMs));
for (var event : events) { for (var event : events) {
tikTokEventHandler.publish(client, new TikTokWebsocketMessageEvent(event, message, metadata)); tikTokEventHandler.publish(client, new TikTokWebsocketMessageEvent(message, event, metadata));
tikTokEventHandler.publish(client, event); tikTokEventHandler.publish(client, event);
} }
} }

View File

@@ -22,35 +22,52 @@
*/ */
package io.github.jwdeveloper.tiktok; 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.messages.data.User; 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.live.LiveRoomInfo;
import io.github.jwdeveloper.tiktok.models.ConnectionState;
import lombok.Data; import lombok.Data;
import java.util.LinkedList;
import java.util.List;
@Data @Data
public class TikTokRoomInfo implements LiveRoomInfo public class TikTokRoomInfo implements LiveRoomInfo {
{
private String roomId; private String roomId;
private int likesCount; private int likesCount;
private int viewersCount; private int viewersCount;
private int totalViewersCount;
private long startTime;
private boolean ageRestricted; private boolean ageRestricted;
private User host; private User host;
private Picture picture; private List<RankingUser> usersRanking = new LinkedList<>();
private String description;
private String hostName; private String hostName;
private String title;
private String language = "en";
private ConnectionState connectionState = ConnectionState.DISCONNECTED; private ConnectionState connectionState = ConnectionState.DISCONNECTED;
public boolean hasConnectionState(ConnectionState state) public boolean hasConnectionState(ConnectionState state) {
{
return connectionState == state; return connectionState == state;
} }
}
@Override
public User getHostUser() {
return host;
}
public void updateRanking(List<RankingUser> rankingUsers) {
usersRanking.clear();
usersRanking.addAll(rankingUsers);
}
}

View File

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

View File

@@ -1,210 +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.handlers;
import io.github.jwdeveloper.tiktok.TikTokRoomInfo;
import io.github.jwdeveloper.tiktok.data.events.*;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent;
import io.github.jwdeveloper.tiktok.data.events.envelop.TikTokChestEvent;
import io.github.jwdeveloper.tiktok.data.events.poll.TikTokPollEndEvent;
import io.github.jwdeveloper.tiktok.data.events.poll.TikTokPollEvent;
import io.github.jwdeveloper.tiktok.data.events.poll.TikTokPollStartEvent;
import io.github.jwdeveloper.tiktok.data.events.poll.TikTokPollUpdateEvent;
import io.github.jwdeveloper.tiktok.data.events.room.TikTokRoomEvent;
import io.github.jwdeveloper.tiktok.data.events.room.TikTokRoomPinEvent;
import io.github.jwdeveloper.tiktok.data.events.room.TikTokRoomUserInfoEvent;
import io.github.jwdeveloper.tiktok.data.events.social.TikTokFollowEvent;
import io.github.jwdeveloper.tiktok.data.events.social.TikTokJoinEvent;
import io.github.jwdeveloper.tiktok.data.events.social.TikTokLikeEvent;
import io.github.jwdeveloper.tiktok.data.events.social.TikTokShareEvent;
import io.github.jwdeveloper.tiktok.data.models.Text;
import io.github.jwdeveloper.tiktok.data.models.chest.Chest;
import io.github.jwdeveloper.tiktok.handlers.events.TikTokGiftEventHandler;
import io.github.jwdeveloper.tiktok.mappers.TikTokGenericEventMapper;
import io.github.jwdeveloper.tiktok.messages.enums.EnvelopeDisplay;
import io.github.jwdeveloper.tiktok.messages.webcast.*;
import io.github.jwdeveloper.tiktok.models.SocialTypes;
import lombok.SneakyThrows;
import java.util.Collections;
import java.util.List;
import java.util.regex.Pattern;
public class TikTokMessageHandlerRegistration extends TikTokMessageHandler {
private final TikTokRoomInfo roomInfo;
private final TikTokGiftEventHandler giftHandler;
private final Pattern socialMediaPattern = Pattern.compile("pm_mt_guidance_viewer_([0-9]+)_share");
public TikTokMessageHandlerRegistration(TikTokEventObserver tikTokEventHandler,
TikTokRoomInfo roomInfo,
TikTokGenericEventMapper genericTikTokEventMapper,
TikTokGiftEventHandler tikTokGiftEventHandler) {
super(tikTokEventHandler, genericTikTokEventMapper);
this.giftHandler = tikTokGiftEventHandler;
this.roomInfo = roomInfo;
init();
}
public void init() {
//ConnectionEvents events
registerMapping(WebcastControlMessage.class, this::handleWebcastControlMessage);
registerMapping(WebcastSystemMessage.class, TikTokRoomEvent.class);
//Room status events
registerMapping(WebcastLiveIntroMessage.class, TikTokRoomEvent.class);
registerMapping(WebcastRoomUserSeqMessage.class, this::handleRoomUserSeqMessage);
registerMapping(RoomMessage.class, TikTokRoomEvent.class);
registerMapping(WebcastRoomMessage.class, TikTokRoomEvent.class);
registerMapping(WebcastCaptionMessage.class, TikTokCaptionEvent.class);
//User Interactions events
registerMapping(WebcastChatMessage.class, TikTokCommentEvent.class);
registerMapping(WebcastLikeMessage.class, this::handleLike);
registerMappings(WebcastGiftMessage.class, giftHandler::handleGift);
registerMapping(WebcastSocialMessage.class, this::handleSocialMedia);
registerMapping(WebcastMemberMessage.class, this::handleMemberMessage);
//Host Interaction events
registerMapping(WebcastPollMessage.class, this::handlePollEvent);
registerMapping(WebcastRoomPinMessage.class, this::handlePinMessage);
registerMapping(WebcastGoalUpdateMessage.class, TikTokGoalUpdateEvent.class);
//LinkMic events
registerMapping(WebcastLinkMicBattle.class, TikTokLinkMicBattleEvent.class);
registerMapping(WebcastLinkMicArmies.class, TikTokLinkMicArmiesEvent.class);
registerMapping(WebcastLinkMicMethod.class, TikTokLinkMicMethodEvent.class);
registerMapping(WebcastLinkMicFanTicketMethod.class, TikTokLinkMicFanTicketEvent.class);
//Rank events
registerMapping(WebcastRankTextMessage.class, TikTokRankTextEvent.class);
registerMapping(WebcastRankUpdateMessage.class, TikTokRankUpdateEvent.class);
registerMapping(WebcastHourlyRankMessage.class, TikTokRankUpdateEvent.class);
//Others events
registerMapping(WebcastInRoomBannerMessage.class, TikTokInRoomBannerEvent.class);
registerMapping(WebcastMsgDetectMessage.class, TikTokDetectEvent.class);
registerMapping(WebcastBarrageMessage.class, TikTokBarrageEvent.class);
registerMapping(WebcastUnauthorizedMemberMessage.class, TikTokUnauthorizedMemberEvent.class);
registerMapping(WebcastOecLiveShoppingMessage.class, TikTokShopEvent.class);
registerMapping(WebcastImDeleteMessage.class, TikTokIMDeleteEvent.class);
registerMapping(WebcastQuestionNewMessage.class, TikTokQuestionEvent.class);
registerMappings(WebcastEnvelopeMessage.class, this::handleEnvelop);
registerMapping(WebcastSubNotifyMessage.class, TikTokSubNotifyEvent.class);
registerMapping(WebcastEmoteChatMessage.class, TikTokEmoteEvent.class);
}
@SneakyThrows
private TikTokEvent handleWebcastControlMessage(byte[] msg) {
var message = WebcastControlMessage.parseFrom(msg);
return switch (message.getAction()) {
case STREAM_PAUSED -> new TikTokLivePausedEvent();
case STREAM_ENDED -> new TikTokLiveEndedEvent();
default -> new TikTokUnhandledControlEvent(message);
};
}
@SneakyThrows
private TikTokEvent handleSocialMedia(byte[] msg) {
var message = WebcastSocialMessage.parseFrom(msg);
var socialType = Text.map(message.getCommon().getDisplayText()).getKey();
var matcher = socialMediaPattern.matcher(socialType);
if (matcher.find()) {
var value = matcher.group(1);
var number = Integer.parseInt(value);
return new TikTokShareEvent(message, number);
}
return switch (socialType) {
case SocialTypes.LikeType -> new TikTokLikeEvent(message, roomInfo.getLikesCount());
case SocialTypes.FollowType -> new TikTokFollowEvent(message);
case SocialTypes.ShareType -> new TikTokShareEvent(message);
case SocialTypes.JoinType -> new TikTokJoinEvent(message, roomInfo.getViewersCount());
default -> new TikTokUnhandledSocialEvent(message);
};
}
@SneakyThrows
private TikTokEvent handleMemberMessage(byte[] msg) {
var message = WebcastMemberMessage.parseFrom(msg);
roomInfo.setViewersCount(message.getMemberCount());
return switch (message.getAction()) {
case JOINED -> new TikTokJoinEvent(message);
case SUBSCRIBED -> new TikTokSubscribeEvent(message);
default -> new TikTokUnhandledMemberEvent(message);
};
}
private TikTokEvent handleRoomUserSeqMessage(byte[] msg) {
var event = (TikTokRoomUserInfoEvent) mapper.mapToEvent(WebcastRoomUserSeqMessage.class, TikTokRoomUserInfoEvent.class, msg);
roomInfo.setViewersCount(event.getTotalUsers());
return event;
}
private TikTokEvent handleLike(byte[] msg) {
var event = (TikTokLikeEvent) mapper.mapToEvent(WebcastLikeMessage.class, TikTokLikeEvent.class, msg);
roomInfo.setLikesCount(event.getTotalLikes());
return event;
}
@SneakyThrows
private TikTokEvent handlePinMessage(byte[] msg) {
var pinMessage = WebcastRoomPinMessage.parseFrom(msg);
var chatMessage = WebcastChatMessage.parseFrom(pinMessage.getPinnedMessage());
var chatEvent = new TikTokCommentEvent(chatMessage);
return new TikTokRoomPinEvent(pinMessage, chatEvent);
}
//TODO Probably not working
@SneakyThrows
private TikTokEvent handlePollEvent(byte[] msg) {
var poolMessage = WebcastPollMessage.parseFrom(msg);
return switch (poolMessage.getMessageType()) {
case 0 -> new TikTokPollStartEvent(poolMessage);
case 1 -> new TikTokPollEndEvent(poolMessage);
case 2 -> new TikTokPollUpdateEvent(poolMessage);
default -> new TikTokPollEvent(poolMessage);
};
}
@SneakyThrows
private List<TikTokEvent> handleEnvelop(byte[] data) {
var msg = WebcastEnvelopeMessage.parseFrom(data);
if (msg.getDisplay() != EnvelopeDisplay.EnvelopeDisplayNew) {
return Collections.emptyList();
}
var totalDiamonds = msg.getEnvelopeInfo().getDiamondCount();
var totalUsers = msg.getEnvelopeInfo().getPeopleCount();
var chest = new Chest(totalDiamonds, totalUsers);
return List.of(new TikTokChestEvent(chest, msg));
}
}

View File

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

View File

@@ -0,0 +1,126 @@
/*
* 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.CookieManager;
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
@AllArgsConstructor
public class HttpClient {
private final HttpClientSettings httpClientSettings;
private final String url;
public <T> Optional<HttpResponse<T>> toResponse(HttpResponse.BodyHandler<T> bodyHandler) {
var client = prepareClient();
var request = prepareGetRequest();
try
{
var response = client.send(request, bodyHandler);
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(HttpResponse.BodyHandlers.ofString());
if (optional.isEmpty()) {
return Optional.empty();
}
var response = optional.get();
var body = response.body();
return Optional.of(body);
}
public Optional<byte[]> toBinaryResponse() {
var optional = toResponse(HttpResponse.BodyHandlers.ofByteArray());
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);
}
private 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();
}
private 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();
}
private 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,87 @@
/*
* 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() {
return new HttpClient(httpClientSettings, url);
}
}

View File

@@ -22,31 +22,21 @@
*/ */
package io.github.jwdeveloper.tiktok.http; package io.github.jwdeveloper.tiktok.http;
import java.util.HashMap; import io.github.jwdeveloper.tiktok.data.settings.LiveClientSettings;
import java.util.Map;
import java.util.Set;
public class TikTokCookieJar { public class HttpClientFactory {
private final Map<String, String> cookies; private final LiveClientSettings liveClientSettings;
public TikTokCookieJar() {
cookies = new HashMap<>(); public HttpClientFactory(LiveClientSettings liveClientSettings) {
this.liveClientSettings = liveClientSettings;
} }
public String get(String key) { public HttpClientBuilder client(String url) {
return cookies.get(key); return new HttpClientBuilder(url, liveClientSettings.getHttpSettings().clone());
} }
public void set(String key, String value) { //Does not contains default httpClientSettings, Params, headers, etd
cookies.put(key, value); public HttpClientBuilder clientEmpty(String url) {
} return new HttpClientBuilder(url);
public String parseCookies()
{
var sb = new StringBuilder();
for(var entry : cookies.entrySet())
{
sb.append(entry.getKey()).append("=").append(entry.getValue()).append(";");
}
return sb.toString();
} }
} }

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,144 +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.exceptions.TikTokLiveException;
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 roomId = fetchRoomIdFromTiktokApi(userName);
clientSettings.getClientParameters().put("room_id", roomId);
logger.info("RoomID -> " + roomId);
return roomId;
}
private String fetchRoomIdFromTikTokPage(String userName)
{
/* var roomId = RequestChain.<String>create()
.then(() -> fetchRoomIdFromTikTokPage(userName))
.then(() -> fetchRoomIdFromTiktokApi(userName))
.run();*/
logger.info("Fetching room ID");
String html;
try {
html = tiktokHttpClient.getLivestreamPage(userName);
} catch (Exception e) {
throw new TikTokLiveRequestException("Failed to fetch room id from WebCast, see stacktrace for more info.", e);
}
var firstPattern = Pattern.compile("room_id=([0-9]*)");
var firstMatcher = firstPattern.matcher(html);
var id = "";
if (firstMatcher.find()) {
id = firstMatcher.group(1);
} else {
var secondPattern = Pattern.compile("\"roomId\":\"([0-9]*)\"");
var secondMatcher = secondPattern.matcher(html);
if (secondMatcher.find()) {
id = secondMatcher.group(1);
}
}
if (id.isEmpty()) {
throw new TikTokLiveOfflineHostException("Unable to fetch room ID, live host could be offline or name is misspelled");
}
return id;
}
private String fetchRoomIdFromTiktokApi(String userName) {
var params = new HashMap<>(clientSettings.getClientParameters());
params.put("uniqueId", userName);
params.put("sourceType", 54);
var roomData = tiktokHttpClient.getJsonFromTikTokApi("api-live/user/room/", params);
var data = roomData.getAsJsonObject("data");
var user =data.getAsJsonObject("user");
var roomId = user.get("roomId").getAsString();
return roomId;
}
public LiveRoomMeta fetchRoomInfo() {
logger.info("Fetching RoomInfo");
try {
var response = tiktokHttpClient.getJsonFromWebcastApi("room/info/", clientSettings.getClientParameters());
var mapper = new LiveRoomMetaMapper();
var liveRoomMeta = mapper.map(response);
logger.info("RoomInfo status -> " + liveRoomMeta.getStatus());
return liveRoomMeta;
} catch (Exception e) {
throw new TikTokLiveRequestException("Failed to fetch room info from WebCast, 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 client data", e);
}
}
}

View File

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

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<>();
}
System.out.println("RomMID: "+parameters.get("room_id"));
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,173 +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.exceptions.TikTokLiveRequestException;
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 String query;
public TikTokHttpRequestFactory(TikTokCookieJar tikTokCookieJar) {
this.tikTokCookieJar = tikTokCookieJar;
this.cookieManager = new CookieManager();
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);
System.out.println(requestUri.toString());
}
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());
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

@@ -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.mappers;
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.data.requests.LiveData;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveRequestException;
import java.util.ArrayList;
public class LiveDataMapper {
/**
* 0 - Unknown
* 1 - ?
* 2 - Online
* 3 - ?
* 4 - Offline
*/
public LiveData.Response map(String json) {
var response = new LiveData.Response();
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 = jsonObject.getAsJsonObject("data");
if (data.has("status")) {
var status = data.get("status");
var statusId = status.getAsInt();
var statusValue = switch (statusId) {
case 2 -> LiveData.LiveStatus.HostOnline;
case 4 -> LiveData.LiveStatus.HostOffline;
default -> LiveData.LiveStatus.HostNotFound;
};
response.setLiveStatus(statusValue);
} else {
response.setLiveStatus(LiveData.LiveStatus.HostNotFound);
}
if (data.has("age_restricted")) {
var element = data.getAsJsonObject("age_restricted");
var restricted = element.get("restricted").getAsBoolean();
response.setAgeRestricted(restricted);
}
if (data.has("title")) {
var element = data.get("title");
var title = element.getAsString();
response.setTitle(title);
}
if (data.has("stats")) {
var statsElement = data.getAsJsonObject("stats");
var likeElement = statsElement.get("like_count");
var likes = likeElement.getAsInt();
var titalUsersElement = statsElement.get("total_user");
var totalUsers = titalUsersElement.getAsInt();
response.setLikes(likes);
response.setTotalViewers(totalUsers);
}
if (data.has("user_count")) {
var element = data.get("user_count");
var viewers = element.getAsInt();
response.setViewers(viewers);
}
if (data.has("owner")) {
var element = data.getAsJsonObject("owner");
var user = getUser(element);
response.setHost(user);
}
return response;
}
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 followers = followElement.get("follower_count").getAsInt();
var followingCount = followElement.get("following_count").getAsInt();
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<>());
user.addAttribute(UserAttribute.LiveHost);
return user;
}
}

View File

@@ -0,0 +1,69 @@
/*
* 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 parsedJson = JsonParser.parseString(json);
var jsonObject = parsedJson.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

@@ -20,17 +20,18 @@
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ */
package io.github.jwdeveloper.tiktok.tools.collector.client; package io.github.jwdeveloper.tiktok.http.mappers;
public class TikTokMessageCollectorClient import com.google.gson.JsonParser;
{ import io.github.jwdeveloper.tiktok.data.requests.SignServerResponse;
public static TikTokMessagessCollectorBuilder create(String outputName)
{
return new TikTokMessagessCollectorBuilder(outputName);
}
public static TikTokMessagessCollectorBuilder create(MessageCollector messageCollector, String outputName) public class SignServerResponseMapper {
{ public SignServerResponse map(String json) {
return new TikTokMessagessCollectorBuilder(outputName); 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,11 +23,11 @@
package io.github.jwdeveloper.tiktok.listener; package io.github.jwdeveloper.tiktok.listener;
import io.github.jwdeveloper.tiktok.annotations.TikTokEventHandler; 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.common.TikTokEvent;
import io.github.jwdeveloper.tiktok.exceptions.TikTokEventListenerMethodException; import io.github.jwdeveloper.tiktok.exceptions.TikTokEventListenerMethodException;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveException; import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveException;
import io.github.jwdeveloper.tiktok.handlers.TikTokEventObserver;
import io.github.jwdeveloper.tiktok.live.LiveClient; import io.github.jwdeveloper.tiktok.live.LiveClient;
import io.github.jwdeveloper.tiktok.live.builder.EventConsumer; import io.github.jwdeveloper.tiktok.live.builder.EventConsumer;
@@ -37,10 +37,10 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
public class TikTokListenersManager implements ListenersManager { public class TikTokListenersManager implements ListenersManager {
private final TikTokEventObserver eventObserver; private final TikTokLiveEventHandler eventObserver;
private final List<ListenerBindingModel> bindingModels; private final List<ListenerBindingModel> bindingModels;
public TikTokListenersManager(List<TikTokEventListener> listeners, TikTokEventObserver tikTokEventHandler) { public TikTokListenersManager(List<TikTokEventListener> listeners, TikTokLiveEventHandler tikTokEventHandler) {
this.eventObserver = tikTokEventHandler; this.eventObserver = tikTokEventHandler;
this.bindingModels = new ArrayList<>(listeners.size()); this.bindingModels = new ArrayList<>(listeners.size());
for (var listener : listeners) { for (var listener : listeners) {
@@ -93,7 +93,7 @@ public class TikTokListenersManager implements ListenersManager {
var clazz = listener.getClass(); var clazz = listener.getClass();
var methods = Arrays.stream(clazz.getDeclaredMethods()).filter(m -> var methods = Arrays.stream(clazz.getDeclaredMethods()).filter(m ->
m.getParameterCount() == 2 && m.getParameterCount() == 2 &&
m.isAnnotationPresent(TikTokEventHandler.class)).toList(); m.isAnnotationPresent(TikTokEventObserver.class)).toList();
var eventsMap = new HashMap<Class<?>, List<EventConsumer<?>>>(); var eventsMap = new HashMap<Class<?>, List<EventConsumer<?>>>();
for (var method : methods) { for (var method : methods) {
var eventClazz = method.getParameterTypes()[1]; var eventClazz = method.getParameterTypes()[1];
@@ -116,8 +116,8 @@ public class TikTokListenersManager implements ListenersManager {
throw new TikTokEventListenerMethodException(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); return new ListenerBindingModel(listener, eventsMap);
} }
} }

View File

@@ -1,68 +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.mappers;
import com.google.gson.JsonObject;
import io.github.jwdeveloper.tiktok.live.LiveRoomMeta;
public class LiveRoomMetaMapper {
/**
* 0 - Unknown
* 1 - ?
* 2 - Online
* 3 - ?
* 4 - Offline
*/
public LiveRoomMeta map(JsonObject input) {
var liveRoomMeta = new LiveRoomMeta();
if (!input.has("data")) {
return liveRoomMeta;
}
var data = input.getAsJsonObject("data");
if (data.has("status")) {
var status = data.get("status");
var statusId = status.getAsInt();
var statusValue = switch (statusId) {
case 0 -> LiveRoomMeta.LiveRoomStatus.HostNotFound;
case 2 -> LiveRoomMeta.LiveRoomStatus.HostOnline;
case 4 -> LiveRoomMeta.LiveRoomStatus.HostOffline;
default-> LiveRoomMeta.LiveRoomStatus.HostNotFound;
};
liveRoomMeta.setStatus(statusValue);
}
else
{
liveRoomMeta.setStatus(LiveRoomMeta.LiveRoomStatus.HostNotFound);
}
if (data.has("age_restricted")) {
var element = data.getAsJsonObject("age_restricted");
var restricted = element.get("restricted").getAsBoolean();
liveRoomMeta.setAgeRestricted(restricted);
}
return liveRoomMeta;
}
}

View File

@@ -74,7 +74,7 @@ public class TikTokGenericEventMapper {
} }
} }
private Method getParsingMethod(Class<?> input) throws NoSuchMethodException { public Method getParsingMethod(Class<?> input) throws NoSuchMethodException {
if (methodCache.containsKey(input)) { if (methodCache.containsKey(input)) {
return methodCache.get(input); return methodCache.get(input);
} }

View File

@@ -0,0 +1,116 @@
/*
* 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.mappers;
import com.google.protobuf.GeneratedMessageV3;
import io.github.jwdeveloper.tiktok.TikTokLive;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent;
import io.github.jwdeveloper.tiktok.mappers.data.MappingAction;
import io.github.jwdeveloper.tiktok.mappers.data.MappingResult;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
public class TikTokLiveMapper implements TikTokMapper {
private final Map<String, TikTokLiveMapperModel> mappers;
private final TikTokMapperHelper mapperUtils;
private final TikTokLiveMapperModel globalMapperModel;
public TikTokLiveMapper(TikTokMapperHelper mapperUtils) {
this.mappers = new HashMap<>();
this.mapperUtils = mapperUtils;
this.globalMapperModel = new TikTokLiveMapperModel("any message");
}
@Override
public TikTokMapperModel forMessage(String messageName) {
if (!isRegistered(messageName)) {
var model = new TikTokLiveMapperModel(messageName);
mappers.put(messageName, model);
}
return mappers.get(messageName);
}
@Override
public TikTokMapperModel forMessage(Class<? extends GeneratedMessageV3> mapperName) {
return forMessage(mapperName.getSimpleName());
}
@Override
public TikTokMapperModel forMessage(String mapperName, MappingAction<MappingResult> onMapping) {
var model = forMessage(mapperName);
model.onMapping(onMapping);
return model;
}
@Override
public TikTokMapperModel forMessage(Class<? extends GeneratedMessageV3> mapperName, MappingAction<MappingResult> onMapping) {
var model = forMessage(mapperName);
model.onMapping(onMapping);
return model;
}
@Override
public TikTokMapperModel forMessage(Class<? extends GeneratedMessageV3> mapperName, Function<byte[], TikTokEvent> onMapping) {
return forMessage(mapperName, (inputBytes, messageName, mapperHelper) -> MappingResult.of(inputBytes, onMapping.apply(inputBytes)));
}
@Override
public TikTokMapperModel forAnyMessage() {
return globalMapperModel;
}
public boolean isRegistered(String mapperName) {
return mappers.containsKey(mapperName);
}
public <T extends GeneratedMessageV3> boolean isRegistered(Class<T> mapperName) {
return mappers.containsKey(mapperName.getSimpleName());
}
public List<TikTokEvent> handleMapping(String messageName, byte[] bytes) {
if (!isRegistered(messageName)) {
return List.of();
}
var mapperModel = mappers.get(messageName);
var inputBytes = mapperModel.getOnBeforeMapping().onMapping(bytes, messageName, mapperUtils);
var globalInputBytes = globalMapperModel.getOnBeforeMapping().onMapping(inputBytes, messageName, mapperUtils);
var mappingResult = mapperModel.getOnMapping().onMapping(globalInputBytes, messageName, mapperUtils);
if (mappingResult == null) {
mappingResult = globalMapperModel.getOnMapping().onMapping(globalInputBytes, messageName, mapperUtils);
}
var afterMappingResult = mapperModel.getOnAfterMapping().apply(mappingResult);
var globalAfterMappingResult = globalMapperModel.getOnAfterMapping().apply(MappingResult.of(mappingResult.getSource(), afterMappingResult));
return globalAfterMappingResult;
}
}

View File

@@ -0,0 +1,82 @@
/*
* 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.mappers;
import com.google.protobuf.GeneratedMessageV3;
import io.github.jwdeveloper.tiktok.exceptions.TikTokMessageMappingException;
import io.github.jwdeveloper.tiktok.utils.JsonUtil;
import io.github.jwdeveloper.tiktok.utils.ProtoBufferObject;
import io.github.jwdeveloper.tiktok.utils.ProtocolUtils;
public class TikTokLiveMapperHelper implements TikTokMapperHelper {
private final TikTokGenericEventMapper genericMapper;
public TikTokLiveMapperHelper(TikTokGenericEventMapper genericMapper) {
this.genericMapper = genericMapper;
}
@Override
public <T extends GeneratedMessageV3> T bytesToWebcastObject(byte[] bytes, Class<T> messageClass) {
try {
var parsingMethod = genericMapper.getParsingMethod(messageClass);
var sourceObject = parsingMethod.invoke(null, bytes);
return (T) sourceObject;
} catch (Exception e) {
throw new TikTokMessageMappingException(messageClass, "can't find parsing method", e);
}
}
@Override
public Object bytesToWebcastObject(byte[] bytes, String messageName) {
try {
var packageName = "io.github.jwdeveloper.tiktok.messages.webcast." + messageName;
var clazz = Class.forName(packageName);
return bytesToWebcastObject(bytes, (Class<? extends GeneratedMessageV3>) clazz);
} catch (Exception e) {
throw new TikTokMessageMappingException(messageName, e);
}
}
@Override
public boolean isMessageHasProtoClass(String messageName) {
try {
var packageName = "io.github.jwdeveloper.tiktok.messages.webcast." + messageName;
Class.forName(packageName);
return true;
} catch (Exception e) {
return false;
}
}
@Override
public ProtoBufferObject bytesToProtoBufferStructure(byte[] bytes) {
return ProtocolUtils.getProtocolBufferStructure(bytes);
}
@Override
public String toJson(Object obj) {
return JsonUtil.toJson(obj);
}
}

View File

@@ -0,0 +1,79 @@
/*
* 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.mappers;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent;
import io.github.jwdeveloper.tiktok.mappers.data.MappingAction;
import io.github.jwdeveloper.tiktok.mappers.data.MappingResult;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
import java.util.function.Function;
@Getter
@Setter
public class TikTokLiveMapperModel implements TikTokMapperModel {
@Getter
private String sourceMessageName;
private MappingAction<byte[]> onBeforeMapping;
private MappingAction<MappingResult> onMapping;
private Function<MappingResult, List<TikTokEvent>> onAfterMapping;
public TikTokLiveMapperModel(String sourceMessageName, MappingAction onMapping) {
this.sourceMessageName = sourceMessageName;
this.onBeforeMapping = (inputBytes, mesasgeName, mapperHelper) -> inputBytes;
this.onMapping = onMapping;
this.onAfterMapping = MappingResult::getEvents;
}
public TikTokLiveMapperModel(String sourceMessageName) {
this.sourceMessageName = sourceMessageName;
this.onBeforeMapping = (inputBytes, mesasgeName, mapperHelper) -> inputBytes;
this.onMapping = (inputBytes, mesasgeName, mapperHelper) -> MappingResult.of(inputBytes, List.of());
this.onAfterMapping = MappingResult::getEvents;
}
@Override
public TikTokMapperModel onBeforeMapping(MappingAction<byte[]> action) {
this.onBeforeMapping = action;
return this;
}
@Override
public TikTokMapperModel onMapping(MappingAction<MappingResult> action) {
this.onMapping = action;
return this;
}
@Override
public TikTokMapperModel onAfterMapping(Function<MappingResult, List<TikTokEvent>> action) {
this.onAfterMapping = action;
return this;
}
}

View File

@@ -0,0 +1,26 @@
/*
* 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.mappers.handlers;
public class TikTokChestEventHandler {
}

View File

@@ -0,0 +1,88 @@
/*
* 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.mappers.handlers;
import io.github.jwdeveloper.tiktok.data.events.*;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent;
import io.github.jwdeveloper.tiktok.data.events.envelop.TikTokChestEvent;
import io.github.jwdeveloper.tiktok.data.events.poll.TikTokPollEndEvent;
import io.github.jwdeveloper.tiktok.data.events.poll.TikTokPollEvent;
import io.github.jwdeveloper.tiktok.data.events.poll.TikTokPollStartEvent;
import io.github.jwdeveloper.tiktok.data.events.poll.TikTokPollUpdateEvent;
import io.github.jwdeveloper.tiktok.data.events.room.TikTokRoomPinEvent;
import io.github.jwdeveloper.tiktok.data.models.chest.Chest;
import io.github.jwdeveloper.tiktok.messages.enums.EnvelopeDisplay;
import io.github.jwdeveloper.tiktok.messages.webcast.*;
import lombok.SneakyThrows;
import java.util.Collections;
import java.util.List;
public class TikTokCommonEventHandler
{
@SneakyThrows
public TikTokEvent handleWebcastControlMessage(byte[] msg) {
var message = WebcastControlMessage.parseFrom(msg);
return switch (message.getAction()) {
case STREAM_PAUSED -> new TikTokLivePausedEvent();
case STREAM_ENDED -> new TikTokLiveEndedEvent();
case STREAM_UNPAUSED -> new TikTokLiveUnpausedEvent();
default -> new TikTokUnhandledControlEvent(message);
};
}
@SneakyThrows
public TikTokEvent handlePinMessage(byte[] msg) {
var pinMessage = WebcastRoomPinMessage.parseFrom(msg);
var chatMessage = WebcastChatMessage.parseFrom(pinMessage.getPinnedMessage());
var chatEvent = new TikTokCommentEvent(chatMessage);
return new TikTokRoomPinEvent(pinMessage, chatEvent);
}
//TODO Probably not working
@SneakyThrows
public TikTokEvent handlePollEvent(byte[] msg) {
var poolMessage = WebcastPollMessage.parseFrom(msg);
return switch (poolMessage.getMessageType()) {
case MESSAGETYPE_SUBSUCCESS -> new TikTokPollStartEvent(poolMessage);
case MESSAGETYPE_ANCHORREMINDER -> new TikTokPollEndEvent(poolMessage);
case MESSAGETYPE_ENTERROOMEXPIRESOON -> new TikTokPollUpdateEvent(poolMessage);
default -> new TikTokPollEvent(poolMessage);
};
}
@SneakyThrows
public List<TikTokEvent> handleEnvelop(byte[] data) {
var msg = WebcastEnvelopeMessage.parseFrom(data);
if (msg.getDisplay() != EnvelopeDisplay.EnvelopeDisplayNew) {
return Collections.emptyList();
}
var totalDiamonds = msg.getEnvelopeInfo().getDiamondCount();
var totalUsers = msg.getEnvelopeInfo().getPeopleCount();
var chest = new Chest(totalDiamonds, totalUsers);
return List.of(new TikTokChestEvent(chest, msg));
}
}

View File

@@ -20,8 +20,9 @@
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ */
package io.github.jwdeveloper.tiktok.handlers.events; package io.github.jwdeveloper.tiktok.mappers.handlers;
import io.github.jwdeveloper.tiktok.TikTokRoomInfo;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent; import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent;
import io.github.jwdeveloper.tiktok.data.events.gift.TikTokGiftComboEvent; import io.github.jwdeveloper.tiktok.data.events.gift.TikTokGiftComboEvent;
import io.github.jwdeveloper.tiktok.data.events.gift.TikTokGiftEvent; import io.github.jwdeveloper.tiktok.data.events.gift.TikTokGiftEvent;
@@ -30,6 +31,8 @@ import io.github.jwdeveloper.tiktok.data.models.gifts.Gift;
import io.github.jwdeveloper.tiktok.data.models.gifts.GiftSendType; import io.github.jwdeveloper.tiktok.data.models.gifts.GiftSendType;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveException; import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveException;
import io.github.jwdeveloper.tiktok.live.GiftManager; import io.github.jwdeveloper.tiktok.live.GiftManager;
import io.github.jwdeveloper.tiktok.mappers.TikTokMapperHelper;
import io.github.jwdeveloper.tiktok.mappers.data.MappingResult;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastGiftMessage; import io.github.jwdeveloper.tiktok.messages.webcast.WebcastGiftMessage;
import lombok.SneakyThrows; import lombok.SneakyThrows;
import sun.misc.Unsafe; import sun.misc.Unsafe;
@@ -41,24 +44,34 @@ import java.util.Map;
public class TikTokGiftEventHandler { public class TikTokGiftEventHandler {
private final GiftManager giftManager; private final GiftManager giftManager;
private final Map<Long, WebcastGiftMessage> giftsMessages; private final Map<Long, WebcastGiftMessage> giftsMessages;
private final TikTokRoomInfo tikTokRoomInfo;
public TikTokGiftEventHandler(GiftManager giftManager) { public TikTokGiftEventHandler(GiftManager giftManager, TikTokRoomInfo tikTokRoomInfo) {
this.giftManager = giftManager; this.giftManager = giftManager;
giftsMessages = new HashMap<>(); giftsMessages = new HashMap<>();
this.tikTokRoomInfo = tikTokRoomInfo;
} }
@SneakyThrows @SneakyThrows
public List<TikTokEvent> handleGift(byte[] msg) { public MappingResult handleGifts(byte[] msg, String name, TikTokMapperHelper helper) {
var currentMessage = WebcastGiftMessage.parseFrom(msg); var currentMessage = WebcastGiftMessage.parseFrom(msg);
return handleGift(currentMessage); var gifts = handleGift(currentMessage);
return MappingResult.of(currentMessage, gifts);
} }
public List<TikTokEvent> handleGift(WebcastGiftMessage currentMessage) public List<TikTokEvent> handleGift(WebcastGiftMessage currentMessage) {
{
var userId = currentMessage.getUser().getId(); var userId = currentMessage.getUser().getId();
var currentType = GiftSendType.fromNumber(currentMessage.getSendType()); var currentType = GiftSendType.fromNumber(currentMessage.getSendType());
var containsPreviousMessage = giftsMessages.containsKey(userId); var containsPreviousMessage = giftsMessages.containsKey(userId);
//If gift is not streakable just return onGift event
if (currentMessage.getGift().getType() != 1) {
var comboEvent = getGiftComboEvent(currentMessage, GiftSendType.Finished);
var giftEvent = getGiftEvent(currentMessage);
return List.of(comboEvent, giftEvent);
}
if (!containsPreviousMessage) { if (!containsPreviousMessage) {
if (currentType == GiftSendType.Finished) { if (currentType == GiftSendType.Finished) {
return List.of(getGiftEvent(currentMessage)); return List.of(getGiftEvent(currentMessage));
@@ -89,26 +102,23 @@ public class TikTokGiftEventHandler {
} }
private TikTokGiftEvent getGiftEvent(WebcastGiftMessage message) { private TikTokGiftEvent getGiftEvent(WebcastGiftMessage message) {
var gift = getGiftObject(message); var gift = getGiftObject(message);
return new TikTokGiftEvent(gift, message); return new TikTokGiftEvent(gift, tikTokRoomInfo.getHost(), message);
} }
private TikTokGiftEvent getGiftComboEvent(WebcastGiftMessage message, GiftSendType state) { private TikTokGiftEvent getGiftComboEvent(WebcastGiftMessage message, GiftSendType state) {
var gift = getGiftObject(message); var gift = getGiftObject(message);
return new TikTokGiftComboEvent(gift, message, state); return new TikTokGiftComboEvent(gift, tikTokRoomInfo.getHost(), message, state);
} }
private Gift getGiftObject(WebcastGiftMessage giftMessage) private Gift getGiftObject(WebcastGiftMessage giftMessage) {
{
var giftId = (int) giftMessage.getGiftId(); var giftId = (int) giftMessage.getGiftId();
var gift = giftManager.findById(giftId); var gift = giftManager.findById(giftId);
if (gift == Gift.UNDEFINED) { if (gift == Gift.UNDEFINED) {
gift = giftManager.findByName(giftMessage.getGift().getName()); gift = giftManager.findByName(giftMessage.getGift().getName());
} }
if (gift == Gift.UNDEFINED) if (gift == Gift.UNDEFINED) {
{
gift = giftManager.registerGift( gift = giftManager.registerGift(
giftId, giftId,
giftMessage.getGift().getName(), giftMessage.getGift().getName(),

View File

@@ -0,0 +1,115 @@
/*
* 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.mappers.handlers;
import io.github.jwdeveloper.tiktok.TikTokRoomInfo;
import io.github.jwdeveloper.tiktok.data.events.TikTokSubscribeEvent;
import io.github.jwdeveloper.tiktok.data.events.TikTokUnhandledMemberEvent;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent;
import io.github.jwdeveloper.tiktok.data.events.room.TikTokRoomInfoEvent;
import io.github.jwdeveloper.tiktok.data.events.social.TikTokJoinEvent;
import io.github.jwdeveloper.tiktok.data.events.social.TikTokLikeEvent;
import io.github.jwdeveloper.tiktok.data.models.RankingUser;
import io.github.jwdeveloper.tiktok.data.models.users.User;
import io.github.jwdeveloper.tiktok.mappers.TikTokMapperHelper;
import io.github.jwdeveloper.tiktok.mappers.data.MappingResult;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastLikeMessage;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastLiveIntroMessage;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastMemberMessage;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastRoomUserSeqMessage;
import lombok.SneakyThrows;
import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Collectors;
public class TikTokRoomInfoEventHandler {
private final TikTokRoomInfo roomInfo;
public TikTokRoomInfoEventHandler(TikTokRoomInfo roomInfo) {
this.roomInfo = roomInfo;
}
public TikTokEvent handleRoomInfo(Consumer<TikTokRoomInfo> consumer) {
consumer.accept(roomInfo);
return new TikTokRoomInfoEvent(roomInfo);
}
@SneakyThrows
public TikTokEvent handleUserRanking(byte[] msg) {
var message = WebcastRoomUserSeqMessage.parseFrom(msg);
var totalUsers = message.getTotalUser();
var userRanking = message.getRanksListList().stream().map(RankingUser::new)
.sorted((ru1, ru2) -> Integer.compare(ru2.getScore(), ru1.getScore()))
.collect(Collectors.toList());
return handleRoomInfo(tikTokRoomInfo ->
{
tikTokRoomInfo.setTotalViewersCount(totalUsers);
tikTokRoomInfo.updateRanking(userRanking);
});
}
@SneakyThrows
public TikTokEvent handleIntro(byte[] msg) {
var message = WebcastLiveIntroMessage.parseFrom(msg);
var hostUser = User.map(message.getHost());
var language = message.getLanguage();
return handleRoomInfo(tikTokRoomInfo ->
{
if (tikTokRoomInfo.getHost() == null) {
tikTokRoomInfo.setHost(hostUser);
}
tikTokRoomInfo.setLanguage(language);
});
}
@SneakyThrows
public MappingResult handleMemberMessage(byte[] msg, String name, TikTokMapperHelper helper) {
var message = WebcastMemberMessage.parseFrom(msg);
var event = switch (message.getAction()) {
case JOINED -> new TikTokJoinEvent(message);
case SUBSCRIBED -> new TikTokSubscribeEvent(message);
default -> new TikTokUnhandledMemberEvent(message);
};
var roomInfoEvent = this.handleRoomInfo(tikTokRoomInfo ->
{
tikTokRoomInfo.setViewersCount(message.getMemberCount());
});
return MappingResult.of(message, List.of(event, roomInfoEvent));
}
@SneakyThrows
public MappingResult handleLike(byte[] msg, String name, TikTokMapperHelper helper) {
var message = WebcastLikeMessage.parseFrom(msg);
var event = new TikTokLikeEvent(message);
var roomInfoEvent = this.handleRoomInfo(tikTokRoomInfo ->
{
tikTokRoomInfo.setLikesCount(event.getTotalLikes());
});
return MappingResult.of(message, List.of(event, roomInfoEvent));
}
}

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.mappers.handlers;
import io.github.jwdeveloper.tiktok.TikTokRoomInfo;
import io.github.jwdeveloper.tiktok.data.events.TikTokUnhandledSocialEvent;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent;
import io.github.jwdeveloper.tiktok.data.events.social.TikTokFollowEvent;
import io.github.jwdeveloper.tiktok.data.events.social.TikTokJoinEvent;
import io.github.jwdeveloper.tiktok.data.events.social.TikTokLikeEvent;
import io.github.jwdeveloper.tiktok.data.events.social.TikTokShareEvent;
import io.github.jwdeveloper.tiktok.data.models.Text;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastSocialMessage;
import io.github.jwdeveloper.tiktok.models.SocialTypes;
import lombok.SneakyThrows;
import java.util.regex.Pattern;
public class TikTokSocialMediaEventHandler
{
private final TikTokRoomInfo roomInfo;
private final Pattern socialMediaPattern = Pattern.compile("pm_mt_guidance_viewer_([0-9]+)_share");
public TikTokSocialMediaEventHandler(TikTokRoomInfo roomInfo) {
this.roomInfo = roomInfo;
}
@SneakyThrows
public TikTokEvent handle(byte[] msg)
{
var message = WebcastSocialMessage.parseFrom(msg);
var socialType = Text.map(message.getCommon().getDisplayText()).getKey();
var matcher = socialMediaPattern.matcher(socialType);
if (matcher.find()) {
var value = matcher.group(1);
var number = Integer.parseInt(value);
return new TikTokShareEvent(message, number);
}
return switch (socialType) {
case SocialTypes.LikeType -> new TikTokLikeEvent(message, roomInfo.getLikesCount());
case SocialTypes.FollowType -> new TikTokFollowEvent(message);
case SocialTypes.ShareType -> new TikTokShareEvent(message);
case SocialTypes.JoinType -> new TikTokJoinEvent(message, roomInfo.getViewersCount());
default -> new TikTokUnhandledSocialEvent(message);
};
}
}

View File

@@ -23,104 +23,71 @@
package io.github.jwdeveloper.tiktok.websocket; package io.github.jwdeveloper.tiktok.websocket;
import io.github.jwdeveloper.tiktok.ClientSettings; import io.github.jwdeveloper.tiktok.data.settings.LiveClientSettings;
import io.github.jwdeveloper.tiktok.Constants;
import io.github.jwdeveloper.tiktok.TikTokLiveClient;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveException; import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveException;
import io.github.jwdeveloper.tiktok.handlers.TikTokEventObserver; import io.github.jwdeveloper.tiktok.TikTokLiveEventHandler;
import io.github.jwdeveloper.tiktok.handlers.TikTokMessageHandlerRegistration; import io.github.jwdeveloper.tiktok.TikTokLiveMessageHandler;
import io.github.jwdeveloper.tiktok.http.HttpUtils; import io.github.jwdeveloper.tiktok.data.requests.LiveConnectionData;
import io.github.jwdeveloper.tiktok.http.TikTokCookieJar;
import io.github.jwdeveloper.tiktok.live.LiveClient; import io.github.jwdeveloper.tiktok.live.LiveClient;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastResponse;
import org.java_websocket.client.WebSocketClient; import org.java_websocket.client.WebSocketClient;
import java.net.URI;
import java.util.HashMap; import java.util.HashMap;
import java.util.TreeMap;
import java.util.logging.Logger;
public class TikTokWebSocketClient implements SocketClient { public class TikTokWebSocketClient implements SocketClient {
private final Logger logger; private final LiveClientSettings clientSettings;
private final ClientSettings clientSettings; private final TikTokLiveMessageHandler messageHandler;
private final TikTokCookieJar tikTokCookieJar; private final TikTokLiveEventHandler tikTokEventHandler;
private final TikTokMessageHandlerRegistration webResponseHandler;
private final TikTokEventObserver tikTokEventHandler;
private WebSocketClient webSocketClient; private WebSocketClient webSocketClient;
private TikTokWebSocketPingingTask pingingTask;
private boolean isConnected; private boolean isConnected;
public TikTokWebSocketClient(Logger logger, public TikTokWebSocketClient(
TikTokCookieJar tikTokCookieJar, LiveClientSettings clientSettings,
ClientSettings clientSettings, TikTokLiveMessageHandler messageHandler,
TikTokMessageHandlerRegistration webResponseHandler, TikTokLiveEventHandler tikTokEventHandler) {
TikTokEventObserver tikTokEventHandler) {
this.logger = logger;
this.tikTokCookieJar = tikTokCookieJar;
this.clientSettings = clientSettings; this.clientSettings = clientSettings;
this.webResponseHandler = webResponseHandler; this.messageHandler = messageHandler;
this.tikTokEventHandler = tikTokEventHandler; this.tikTokEventHandler = tikTokEventHandler;
isConnected = false; isConnected = false;
} }
@Override
public void start(LiveConnectionData.Response connectionData, LiveClient liveClient)
{
public void start(WebcastResponse webcastResponse, LiveClient tikTokLiveClient) {
if (isConnected) { if (isConnected) {
stop(); stop();
} }
if (webcastResponse.getPushServer().isEmpty() || webcastResponse.getRouteParamsMapMap().isEmpty()) 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);
try
{ {
throw new TikTokLiveException("Could not find Room");
}
try {
if (clientSettings.isHandleExistingEvents()) {
logger.info("Handling existing events");
webResponseHandler.handle(tikTokLiveClient, webcastResponse);
}
var url = getWebSocketUrl(webcastResponse);
webSocketClient = startWebSocket(url, tikTokLiveClient);
webSocketClient.connect(); webSocketClient.connect();
pingingTask = new TikTokWebSocketPingingTask();
pingingTask.run(webSocketClient);
isConnected = true; isConnected = true;
} catch (Exception e) { } catch (Exception e)
{
isConnected = false; isConnected = false;
throw new TikTokLiveException("Failed to connect to the websocket", e); 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);
}
private WebSocketClient startWebSocket(URI url, LiveClient liveClient) {
var cookie = tikTokCookieJar.parseCookies(); public void stop() {
var headers = new HashMap<String, String>();
headers.put("Cookie", cookie);
return new TikTokWebSocketListener(url,
headers,
3000,
webResponseHandler,
tikTokEventHandler,
liveClient);
}
public void stop()
{
if (isConnected && webSocketClient != null) { if (isConnected && webSocketClient != null) {
webSocketClient.closeConnection(0, ""); webSocketClient.closeConnection(0, "");
pingingTask.stop();
} }
webSocketClient = null; webSocketClient = null;
pingingTask = null;
isConnected = false; isConnected = false;
} }
} }

View File

@@ -27,12 +27,11 @@ import io.github.jwdeveloper.tiktok.data.events.TikTokConnectedEvent;
import io.github.jwdeveloper.tiktok.data.events.TikTokDisconnectedEvent; import io.github.jwdeveloper.tiktok.data.events.TikTokDisconnectedEvent;
import io.github.jwdeveloper.tiktok.data.events.TikTokErrorEvent; import io.github.jwdeveloper.tiktok.data.events.TikTokErrorEvent;
import io.github.jwdeveloper.tiktok.exceptions.TikTokProtocolBufferException; import io.github.jwdeveloper.tiktok.exceptions.TikTokProtocolBufferException;
import io.github.jwdeveloper.tiktok.handlers.TikTokEventObserver; import io.github.jwdeveloper.tiktok.TikTokLiveEventHandler;
import io.github.jwdeveloper.tiktok.handlers.TikTokMessageHandlerRegistration; import io.github.jwdeveloper.tiktok.TikTokLiveMessageHandler;
import io.github.jwdeveloper.tiktok.live.LiveClient; import io.github.jwdeveloper.tiktok.live.LiveClient;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastPushFrame; import io.github.jwdeveloper.tiktok.messages.webcast.WebcastPushFrame;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastResponse; import io.github.jwdeveloper.tiktok.messages.webcast.WebcastResponse;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastWebsocketAck;
import org.java_websocket.client.WebSocketClient; import org.java_websocket.client.WebSocketClient;
import org.java_websocket.drafts.Draft_6455; import org.java_websocket.drafts.Draft_6455;
import org.java_websocket.handshake.ServerHandshake; import org.java_websocket.handshake.ServerHandshake;
@@ -44,80 +43,81 @@ import java.util.Optional;
public class TikTokWebSocketListener extends WebSocketClient { public class TikTokWebSocketListener extends WebSocketClient {
private final TikTokMessageHandlerRegistration webResponseHandler; private final TikTokLiveMessageHandler messageHandler;
private final TikTokEventObserver tikTokEventHandler; private final TikTokLiveEventHandler tikTokEventHandler;
private final LiveClient tikTokLiveClient; private final LiveClient tikTokLiveClient;
public TikTokWebSocketListener(URI serverUri, public TikTokWebSocketListener(URI serverUri,
Map<String, String> httpHeaders, Map<String, String> httpHeaders,
int connectTimeout, int connectTimeout,
TikTokMessageHandlerRegistration webResponseHandler, TikTokLiveMessageHandler messageHandler,
TikTokEventObserver tikTokEventHandler, TikTokLiveEventHandler tikTokEventHandler,
LiveClient tikTokLiveClient) { LiveClient tikTokLiveClient) {
super(serverUri, new Draft_6455(), httpHeaders,connectTimeout); super(serverUri, new Draft_6455(), httpHeaders, connectTimeout);
this.webResponseHandler = webResponseHandler; this.messageHandler = messageHandler;
this.tikTokEventHandler = tikTokEventHandler; this.tikTokEventHandler = tikTokEventHandler;
this.tikTokLiveClient = tikTokLiveClient; this.tikTokLiveClient = tikTokLiveClient;
} }
@Override @Override
public void onOpen(ServerHandshake serverHandshake) { public void onMessage(ByteBuffer bytes) {
tikTokEventHandler.publish(tikTokLiveClient,new TikTokConnectedEvent());
if(isNotClosing())
{
sendPing();
}
}
@Override
public void onMessage(ByteBuffer bytes)
{
try { try {
handleBinary(bytes.array()); handleBinary(bytes.array());
} catch (Exception e) { } catch (Exception e) {
tikTokEventHandler.publish(tikTokLiveClient, new TikTokErrorEvent(e)); tikTokEventHandler.publish(tikTokLiveClient, new TikTokErrorEvent(e));
} }
if(isNotClosing()) 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())
{
sendPing(); sendPing();
} }
} }
private void handleBinary(byte[] buffer) { private void handleBinary(byte[] buffer) {
var websocketMessageOptional = getWebcastWebsocketMessage(buffer); var websocketPushFrameOptional = getWebcastPushFrame(buffer);
if (websocketMessageOptional.isEmpty()) { if (websocketPushFrameOptional.isEmpty()) {
return; return;
} }
var websocketMessage = websocketMessageOptional.get(); var websocketPushFrame = websocketPushFrameOptional.get();
var webResponse = getWebResponseMessage(websocketMessage.getPayload()); var webcastResponse = getWebResponseMessage(websocketPushFrame.getPayload());
if(webResponse.getNeedsAck()) if (webcastResponse.getNeedsAck()) {
{ var pushFrameBuilder = WebcastPushFrame.newBuilder();
//For some reason while send ack id, server get disconnected pushFrameBuilder.setPayloadType("ack");
// sendAckId(webResponse.getFetchInterval()); pushFrameBuilder.setLogId(websocketPushFrame.getLogId());
pushFrameBuilder.setPayload(webcastResponse.getInternalExtBytes());
if (isNotClosing())
{
this.send(pushFrameBuilder.build().toByteArray());
}
} }
messageHandler.handle(tikTokLiveClient, webcastResponse);
webResponseHandler.handle(tikTokLiveClient, webResponse);
} }
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 i, String s, boolean b) {
tikTokEventHandler.publish(tikTokLiveClient, new TikTokDisconnectedEvent());
}
@Override
public void onError(Exception error) {
tikTokEventHandler.publish(tikTokLiveClient, new TikTokErrorEvent(error));
if (isNotClosing()) {
sendPing();
}
}
private Optional<WebcastPushFrame> getWebcastPushFrame(byte[] buffer) {
try { try {
var websocketMessage = WebcastPushFrame.parseFrom(buffer); var websocketMessage = WebcastPushFrame.parseFrom(buffer);
if (websocketMessage.getPayload().isEmpty()) { if (websocketMessage.getPayload().isEmpty()) {
@@ -137,25 +137,10 @@ public class TikTokWebSocketListener extends WebSocketClient {
} }
} }
private boolean isNotClosing() private boolean isNotClosing() {
{
return !isClosed() && !isClosing(); 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 @Override
public void onMessage(String s) { public void onMessage(String s) {

View File

@@ -1,81 +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.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;
public void run(WebSocket webSocket)
{
stop();
thread = new Thread(() ->
{
pingTask(webSocket);
});
isRunning =true;
thread.start();
}
public void stop()
{
if(thread != null)
{
thread.interrupt();
}
isRunning = false;
}
private void pingTask(WebSocket webSocket)
{
var random = new Random();
while (isRunning)
{
try
{
if(!webSocket.isOpen())
{
Thread.sleep(100);
continue;
}
webSocket.sendPing();
var timeout = random.nextInt(MAX_TIMEOUT)+MIN_TIMEOUT;
Thread.sleep(timeout);
}
catch (Exception e)
{
isRunning = false;
}
}
}
}

View File

@@ -69,4 +69,7 @@ public class TikTokGiftManagerTest {
} }
} }

View File

@@ -22,9 +22,13 @@
*/ */
package io.github.jwdeveloper.tiktok.handlers.events; package io.github.jwdeveloper.tiktok.handlers.events;
import io.github.jwdeveloper.tiktok.TikTokRoomInfo;
import io.github.jwdeveloper.tiktok.data.events.gift.TikTokGiftComboEvent;
import io.github.jwdeveloper.tiktok.data.events.gift.TikTokGiftEvent; import io.github.jwdeveloper.tiktok.data.events.gift.TikTokGiftEvent;
import io.github.jwdeveloper.tiktok.data.models.Picture; import io.github.jwdeveloper.tiktok.data.models.Picture;
import io.github.jwdeveloper.tiktok.data.models.gifts.GiftSendType;
import io.github.jwdeveloper.tiktok.gifts.TikTokGiftManager; import io.github.jwdeveloper.tiktok.gifts.TikTokGiftManager;
import io.github.jwdeveloper.tiktok.mappers.handlers.TikTokGiftEventHandler;
import io.github.jwdeveloper.tiktok.messages.data.GiftStruct; import io.github.jwdeveloper.tiktok.messages.data.GiftStruct;
import io.github.jwdeveloper.tiktok.messages.data.Image; import io.github.jwdeveloper.tiktok.messages.data.Image;
import io.github.jwdeveloper.tiktok.messages.data.User; import io.github.jwdeveloper.tiktok.messages.data.User;
@@ -46,21 +50,57 @@ class TikTokGiftEventHandlerTest {
@BeforeAll @BeforeAll
public void before() { public void before() {
var manager = new TikTokGiftManager(Logger.getLogger("x")); var manager = new TikTokGiftManager(Logger.getLogger("x"));
var info = new TikTokRoomInfo();
info.setHost(new io.github.jwdeveloper.tiktok.data.models.users.User(123L, "test", new Picture("")));
manager.registerGift(123, "example", 123, new Picture("image.webp")); manager.registerGift(123, "example", 123, new Picture("image.webp"));
handler = new TikTokGiftEventHandler(manager); handler = new TikTokGiftEventHandler(manager, info);
} }
@Test @Test
void shouldHandleGifts() { void shouldHandleGifts() {
var message = getGiftMessage("example-new-name", 123, "image-new.png", 0, 1); var message = getGiftMessage("example-new-name", 123, "image-new.png", 0, 1, false);
var result = handler.handleGift(message);
Assertions.assertEquals(2, result.size());
var event = (TikTokGiftEvent) result.get(0);
var gift = event.getGift();
Assertions.assertEquals("image-new.png", gift.getPicture().getLink());
Assertions.assertEquals(123, gift.getId());
}
@Test
void shouldHandleStrakableGift() {
var message = getGiftMessage("example-new-name", 123, "image-new.png", 0, 1, true);
var result = handler.handleGift(message); var result = handler.handleGift(message);
Assertions.assertEquals(1, result.size()); Assertions.assertEquals(1, result.size());
var event = (TikTokGiftEvent) result.get(0); var event = (TikTokGiftEvent) result.get(0);
var gift = event.getGift(); var gift = event.getGift();
Assertions.assertEquals("image-new.png",gift.getPicture().getLink()); Assertions.assertEquals("image-new.png", gift.getPicture().getLink());
Assertions.assertEquals(123,gift.getId()); Assertions.assertEquals(123, gift.getId());
}
@Test
void shouldHandleStrike() {
var message1 = getGiftMessage("example-new-name", 123, "image-new.png", 1, 1, true);
var message2 = getGiftMessage("example-new-name", 123, "image-new.png", 2, 1, true);
var message3 = getGiftMessage("example-new-name", 123, "image-new.png", 0, 1, true);
var result1 = handler.handleGift(message1);
var result2 = handler.handleGift(message2);
var result3 = handler.handleGift(message3);
var event1 = (TikTokGiftComboEvent) result1.get(0);
var event2 = (TikTokGiftComboEvent) result2.get(0);
Assertions.assertEquals(2, result3.size());
var event3 = (TikTokGiftComboEvent) result3.get(0);
Assertions.assertEquals(GiftSendType.Begin, event1.getComboState());
Assertions.assertEquals(GiftSendType.Active, event2.getComboState());
Assertions.assertEquals(GiftSendType.Finished, event3.getComboState());
} }
@@ -68,7 +108,8 @@ class TikTokGiftEventHandlerTest {
int giftId, int giftId,
String giftImage, String giftImage,
int sendType, int sendType,
int userId) { int userId,
boolean streakable) {
var builder = WebcastGiftMessage.newBuilder(); var builder = WebcastGiftMessage.newBuilder();
var giftBuilder = GiftStruct.newBuilder(); var giftBuilder = GiftStruct.newBuilder();
var userBuilder = User.newBuilder(); var userBuilder = User.newBuilder();
@@ -77,6 +118,7 @@ class TikTokGiftEventHandlerTest {
giftBuilder.setId(giftId); giftBuilder.setId(giftId);
giftBuilder.setName(giftName); giftBuilder.setName(giftName);
giftBuilder.setImage(Image.newBuilder().addUrlList(giftImage).build()); giftBuilder.setImage(Image.newBuilder().addUrlList(giftImage).build());
giftBuilder.setType(streakable ? 1 : 0);
userBuilder.setId(userId); userBuilder.setId(userId);
builder.setGiftId(giftId); builder.setGiftId(giftId);
@@ -86,4 +128,5 @@ class TikTokGiftEventHandlerTest {
return builder.build(); return builder.build();
} }
} }

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,18 +0,0 @@
package io.github.jwdeveloper.tiktok.http;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
public class TikTokLiveOnlineCheckerTest {
private final String TARGET_USER = "bangbetmenygy";
@Test
public void shouldTestOnline() {
var sut = new TikTokDataChecker();
var result = sut.isOnline(TARGET_USER);
Assertions.assertTrue(result);
}
}

View File

@@ -22,12 +22,12 @@
*/ */
package io.github.jwdeveloper.tiktok.listener; package io.github.jwdeveloper.tiktok.listener;
import io.github.jwdeveloper.tiktok.annotations.TikTokEventHandler; 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.common.TikTokEvent;
import io.github.jwdeveloper.tiktok.data.events.gift.TikTokGiftEvent; import io.github.jwdeveloper.tiktok.data.events.gift.TikTokGiftEvent;
import io.github.jwdeveloper.tiktok.data.events.social.TikTokJoinEvent; import io.github.jwdeveloper.tiktok.data.events.social.TikTokJoinEvent;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveException; import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveException;
import io.github.jwdeveloper.tiktok.handlers.TikTokEventObserver;
import io.github.jwdeveloper.tiktok.live.LiveClient; import io.github.jwdeveloper.tiktok.live.LiveClient;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@@ -42,12 +42,12 @@ import static org.mockito.Mockito.verify;
class TikTokListenersManagerTest { class TikTokListenersManagerTest {
private TikTokEventObserver eventObserver; private TikTokLiveEventHandler eventObserver;
private TikTokListenersManager tikTokListenersManager; private TikTokListenersManager tikTokListenersManager;
@BeforeEach @BeforeEach
void setUp() { void setUp() {
eventObserver = Mockito.mock(TikTokEventObserver.class); eventObserver = Mockito.mock(TikTokLiveEventHandler.class);
List<TikTokEventListener> listeners = new ArrayList<>(); List<TikTokEventListener> listeners = new ArrayList<>();
tikTokListenersManager = new TikTokListenersManager(listeners, eventObserver); tikTokListenersManager = new TikTokListenersManager(listeners, eventObserver);
} }
@@ -93,19 +93,19 @@ class TikTokListenersManagerTest {
public static class TikTokEventListenerTest implements TikTokEventListener public static class TikTokEventListenerTest implements TikTokEventListener
{ {
@TikTokEventHandler @TikTokEventObserver
public void onJoin(LiveClient client, TikTokJoinEvent joinEvent) public void onJoin(LiveClient client, TikTokJoinEvent joinEvent)
{ {
} }
@TikTokEventHandler @TikTokEventObserver
public void onGift(LiveClient client, TikTokGiftEvent giftMessageEvent) public void onGift(LiveClient client, TikTokGiftEvent giftMessageEvent)
{ {
} }
@TikTokEventHandler @TikTokEventObserver
public void onEvent(LiveClient client, TikTokEvent event) public void onEvent(LiveClient client, TikTokEvent event)
{ {

View File

@@ -41,7 +41,7 @@
<parent> <parent>
<artifactId>TikTokLiveJava</artifactId> <artifactId>TikTokLiveJava</artifactId>
<groupId>io.github.jwdeveloper.tiktok</groupId> <groupId>io.github.jwdeveloper.tiktok</groupId>
<version>1.0.3-Release</version> <version>1.0.15-Release</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

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