Compare commits

...

50 Commits

Author SHA1 Message Date
Jacek W
a8ac0f6fff Update maven-publish.yml 2024-10-28 01:32:58 +01:00
David Kohler
732eda3d22 Merge pull request #106 from jwdeveloper/develop-1.8.6
Develop 1.8.6
2024-10-27 20:28:59 -04:00
kohlerpop1
358954984b Deprecate the usage of github gifts json! 2024-10-18 14:14:36 -04:00
kohlerpop1
acb13ea58a Removal of examples using Deprecated TikTokEventListener pt 2! 2024-10-17 21:51:07 -04:00
kohlerpop1
e8129862d1 Removal of examples using Deprecated TikTokEventListener! 2024-10-17 21:48:42 -04:00
kohlerpop1
88e36e7e90 Add settings as parameter to TikTokLiveRecorderStartedEvent! 2024-10-17 21:10:08 -04:00
kohlerpop1
eef9d43d01 Added TikTokLiveRecorderEndedEvent and updated RecorderListener to use CompletableFuture and a cancellation token! 2024-10-17 16:53:00 -04:00
kohlerpop1
75f3896a86 Merge branch 'master' into develop-1.8.6 2024-10-11 14:18:38 -04:00
Jacek W
e679ff23b7 Update README.md 2024-10-11 19:27:58 +02:00
kohlerpop1
e6211e69c5 Removed implements TikTokEventListener from TikTokEventListenerBase
Added current viewers to TikTokRoomInfoEventHandler
2024-10-09 18:06:28 -04:00
kohlerpop1
1b3ee24aea Add STREAM_SUSPENDED proto and use it to fire stream ended event! 2024-10-08 21:41:04 -04:00
GitHub Action
c9309ee2cf Update version in pom.xml 2024-09-02 14:51:14 +00:00
Jacek W
a28df44cee Merge pull request #101 from kMagic142/master
replaced asList() method call to old fashioned iteration
2024-09-02 16:49:29 +02:00
kMagic142
ee866762a3 replaced asList() method call to old fashioned iteration to avoid gson errors 2024-09-01 02:30:15 +03:00
GitHub Action
3cb7c83ef4 Update version in pom.xml 2024-08-23 07:25:27 +00:00
Jacek W
bf755bcb89 Merge pull request #98 from lulajax/fix-issue-97
adjust the position of the followingCount and followers
2024-08-23 09:23:49 +02:00
lujunjie
20bb05e527 adjust the position of the followingCount and followers 2024-08-23 15:13:53 +08:00
Jacek W
d5a3213ad8 Update README.md 2024-08-20 20:47:17 +02:00
Jacek W
7e496373ef Update README.md 2024-08-20 20:47:00 +02:00
GitHub Action
1f3c1c7d41 Update version in pom.xml 2024-07-31 02:47:35 +00:00
David Kohler
c160259863 Merge pull request #94 from jwdeveloper/develop-1.8.5
Develop 1.8.5
2024-07-30 22:45:49 -04:00
kohlerpop1
fb458e7e7d Rename pinging-task to heartbeat-task! 2024-07-30 20:36:45 -04:00
kohlerpop1
5f5ada312a Renamed PingTask to HeartbeatTask to reflect discovered byte array of TikTok's custom heartbeat value 2024-07-30 16:09:50 -04:00
GitHub Action
cb20c3dd3a Update version in pom.xml 2024-07-27 23:20:36 +00:00
David Kohler
bf42f65b3d Merge pull request #93 from jwdeveloper/develop-1.8.4
Added User instance to LiveUserDataMapper Response to access the retrieved user
2024-07-27 19:18:58 -04:00
kohlerpop1
05e18ef8e0 Added User instance to LiveUserDataMapper Response to access the retrieved user. 2024-07-27 13:02:31 -04:00
GitHub Action
0f6ee58d7f Update version in pom.xml 2024-07-21 17:03:47 +00:00
Jacek W
511759960d Merge pull request #91 from jwdeveloper/develop-1.8.3
- fix tests
2024-07-21 19:02:02 +02:00
JW
617d1c381a - fix tests 2024-07-21 19:01:11 +02:00
Jacek W
6f8b4698cd Merge pull request #90 from jwdeveloper/develop-1.8.3
Develop 1.8.3
2024-07-21 18:56:48 +02:00
JW
2bc1993ea5 - Create of methods for more events
- User.name was empty in testing event
- Add `onConnecting` to builder
2024-07-21 12:08:27 +02:00
kohlerpop1
3d4a517adb Removal of TikTokRoomDataResponseEvent.java as its included in TikTokPreConnectionEvent.java
Added ListUser#toString
Changed TikTokLiveHttpClient.getStartingPayload and TikTokLiveHttpClient.getByteResponse to protected
Altered DownloadData to be dynamic to declare request values or append them instead!
2024-07-18 17:41:27 -04:00
GitHub Action
ee0b559758 Update version in pom.xml 2024-07-06 15:26:51 +00:00
GitHub Action
1c9573dc39 Update version in pom.xml 2024-07-06 15:13:34 +00:00
Jacek W
5b17c33236 Merge pull request #89 from jwdeveloper/develop-1.8.2
Develop 1.8.2
2024-07-06 17:12:01 +02:00
kohlerpop1
b59373254c Make TikTokLinkMicBattleEvent.is1v1 and is2v2 dynamic based upon team instance
Moved Team1v1 and Team2v2 totalPoints to Team for universal access
2024-07-05 16:21:42 -04:00
jacek.wolniewicz
5524d9f8c4 -- tests improvement 2024-07-05 13:23:56 +02:00
jacek.wolniewicz
aa56f8eaea -- tests improvement 2024-07-05 13:21:59 +02:00
jacek.wolniewicz
e40bde8e7c - clean up code
- rename module Example to examples
2024-07-04 17:40:22 +02:00
jacek.wolniewicz
b769eb9c1f Merge remote-tracking branch 'origin/develop-1.8.2' into develop-1.8.2 2024-07-04 11:52:26 +02:00
jacek.wolniewicz
3e555a502a - add comments next to magic numbers 2024-07-04 11:51:45 +02:00
kohlerpop1
316868818b Renamed Priority.priorityValue to value to follow enum standards
Clarified message in TikTokEventListener
2024-07-03 21:04:44 -04:00
jacek.wolniewicz
36475c2cf6 - change method name from onMapping, to mappings 2024-07-04 00:00:26 +02:00
jacek.wolniewicz
2dbe81278c Merge branch 'refs/heads/master' into develop-1.8.2 2024-07-03 23:45:11 +02:00
jacek.wolniewicz
91fc32b20b Improvement on the ListenersManager 2024-07-03 23:43:23 +02:00
jacek.wolniewicz
fa855fa3aa Improvement on the ListenersManager 2024-07-03 23:40:48 +02:00
jacek.wolniewicz
cfea12dacc Remove descrabble 2024-07-03 22:37:40 +02:00
Jacek W
16cd819aaf Merge pull request #88 from jwdeveloper/develop-1.8.1
Develop 1.8.1
2024-07-03 22:30:18 +02:00
jacek.wolniewicz
290a4970fd Improvement of Listener 2024-07-03 22:29:11 +02:00
jacek.wolniewicz
660cb287e9 Improvement of Listener 2024-07-03 22:27:15 +02:00
82 changed files with 708 additions and 424 deletions

View File

@@ -53,7 +53,7 @@ jobs:
restore-keys: ${{runner.os}}-m2 restore-keys: ${{runner.os}}-m2
- name: 7 Create Artifacts - name: 7 Create Artifacts
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v4
with: with:
name: ${{ steps.version.outputs.version_tag }} name: ${{ steps.version.outputs.version_tag }}
path: staging path: staging

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.8.0-Release</version> <version>1.8.7-Release</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<artifactId>API</artifactId> <artifactId>API</artifactId>

View File

@@ -0,0 +1,18 @@
package io.github.jwdeveloper.tiktok.annotations;
/**
* HIGHEST 1
* HIGH 2
* NORMAL 3
* LOW 4
* LOWEST 5
*/
public enum Priority {
LOWEST(2), LOW(1), NORMAL(0), HIGH(-1), HIGHEST(-2);
public final int value;
Priority(int value) {
this.value = value;
}
}

View File

@@ -28,5 +28,14 @@ import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
public @interface TikTokEventObserver public @interface TikTokEventObserver
{ {
/**
* When more than one method listen for certain Event, you can specify the method priority
* @see Priority
*/
Priority priority() default Priority.NORMAL;
/**
* When true, action is invoked on a thread, from the threads pool
*/
boolean async() default false;
} }

View File

@@ -58,6 +58,7 @@ public class TikTokCommentEvent extends TikTokHeaderEvent {
var builder = WebcastChatMessage.newBuilder(); var builder = WebcastChatMessage.newBuilder();
builder.setUser(io.github.jwdeveloper.tiktok.messages.data.User.newBuilder() builder.setUser(io.github.jwdeveloper.tiktok.messages.data.User.newBuilder()
.setNickname(userName) .setNickname(userName)
.setDisplayId(userName)
.build()); .build());
builder.setContentLanguage("en"); builder.setContentLanguage("en");
builder.setVisibleToSender(true); builder.setVisibleToSender(true);

View File

@@ -29,4 +29,9 @@ import io.github.jwdeveloper.tiktok.data.events.common.TikTokLiveClientEvent;
public class TikTokConnectedEvent extends TikTokLiveClientEvent public class TikTokConnectedEvent extends TikTokLiveClientEvent
{ {
public static TikTokConnectedEvent of()
{
return new TikTokConnectedEvent();
}
} }

View File

@@ -39,4 +39,10 @@ public class TikTokDisconnectedEvent extends TikTokLiveClientEvent {
public TikTokDisconnectedEvent() { public TikTokDisconnectedEvent() {
this("None"); this("None");
} }
public static TikTokDisconnectedEvent of(String reason)
{
return new TikTokDisconnectedEvent(reason);
}
} }

View File

@@ -25,13 +25,18 @@ package io.github.jwdeveloper.tiktok.data.events;
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.TikTokLiveClientEvent; import io.github.jwdeveloper.tiktok.data.events.common.TikTokLiveClientEvent;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveException;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
@Getter @Getter
@AllArgsConstructor @AllArgsConstructor
@EventMeta(eventType = EventType.Control) @EventMeta(eventType = EventType.Control)
public class TikTokErrorEvent extends TikTokLiveClientEvent public class TikTokErrorEvent extends TikTokLiveClientEvent {
{
private final Throwable exception; private final Throwable exception;
public static TikTokErrorEvent of(String message) {
return new TikTokErrorEvent(new TikTokLiveException(message));
}
} }

View File

@@ -43,8 +43,6 @@ public class TikTokLinkMicBattleEvent extends TikTokHeaderEvent
true if battle is finished otherwise false true if battle is finished otherwise false
*/ */
private final boolean finished; private final boolean finished;
@Getter(AccessLevel.NONE)
private final boolean oneVsOne;
private final List<Team> teams; private final List<Team> teams;
public TikTokLinkMicBattleEvent(WebcastLinkMicBattle msg) { public TikTokLinkMicBattleEvent(WebcastLinkMicBattle msg) {
@@ -55,7 +53,6 @@ public class TikTokLinkMicBattleEvent extends TikTokHeaderEvent
if (msg.getHostTeamCount() == 2) { // 1v1 battle if (msg.getHostTeamCount() == 2) { // 1v1 battle
teams.add(new Team1v1(msg.getHostTeam(0), msg)); teams.add(new Team1v1(msg.getHostTeam(0), msg));
teams.add(new Team1v1(msg.getHostTeam(1), msg)); teams.add(new Team1v1(msg.getHostTeam(1), msg));
oneVsOne = true;
} else { // 2v2 battle } else { // 2v2 battle
if (isFinished()) { if (isFinished()) {
teams.add(new Team2v2(msg.getHostData2V2List().stream().filter(data -> data.getTeamNumber() == 1).findFirst().orElse(null), msg)); teams.add(new Team2v2(msg.getHostData2V2List().stream().filter(data -> data.getTeamNumber() == 1).findFirst().orElse(null), msg));
@@ -64,7 +61,6 @@ public class TikTokLinkMicBattleEvent extends TikTokHeaderEvent
teams.add(new Team2v2(msg.getHostTeam(0), msg.getHostTeam(1), msg)); teams.add(new Team2v2(msg.getHostTeam(0), msg.getHostTeam(1), msg));
teams.add(new Team2v2(msg.getHostTeam(2), msg.getHostTeam(3), msg)); teams.add(new Team2v2(msg.getHostTeam(2), msg.getHostTeam(3), msg));
} }
oneVsOne = false;
} }
// Info: // Info:
@@ -74,10 +70,14 @@ public class TikTokLinkMicBattleEvent extends TikTokHeaderEvent
} }
public boolean is1v1() { public boolean is1v1() {
return oneVsOne; return teams.get(0) instanceof Team1v1;
} }
public boolean is2v2() { public boolean is2v2() {
return !oneVsOne; return teams.get(0) instanceof Team2v2;
}
public boolean isTie() {
return isFinished() && teams.get(0).getTotalPoints() == teams.get(1).getTotalPoints();
} }
} }

View File

@@ -24,9 +24,14 @@ package io.github.jwdeveloper.tiktok.data.events;
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.TikTokEvent; import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent;
import io.github.jwdeveloper.tiktok.data.events.control.TikTokConnectingEvent;
@EventMeta(eventType = EventType.Message) @EventMeta(eventType = EventType.Message)
public class TikTokLiveEndedEvent extends TikTokEvent { public class TikTokLiveEndedEvent extends TikTokEvent {
public static TikTokLiveEndedEvent of() {
return new TikTokLiveEndedEvent();
}
} }

View File

@@ -53,6 +53,7 @@ public class TikTokSubscribeEvent extends TikTokHeaderEvent {
public static TikTokSubscribeEvent of(String userName) { public static TikTokSubscribeEvent of(String userName) {
return new TikTokSubscribeEvent(WebcastMemberMessage.newBuilder() return new TikTokSubscribeEvent(WebcastMemberMessage.newBuilder()
.setUser(io.github.jwdeveloper.tiktok.messages.data.User.newBuilder() .setUser(io.github.jwdeveloper.tiktok.messages.data.User.newBuilder()
.setDisplayId(userName)
.setNickname(userName) .setNickname(userName)
.build()) .build())
.build()); .build());

View File

@@ -31,7 +31,9 @@ import io.github.jwdeveloper.tiktok.data.events.common.TikTokLiveClientEvent;
* Triggered when client is connecting to live is successfully established. * Triggered when client is connecting to live is successfully established.
*/ */
@EventMeta(eventType = EventType.Control) @EventMeta(eventType = EventType.Control)
public class TikTokConnectingEvent extends TikTokLiveClientEvent public class TikTokConnectingEvent extends TikTokLiveClientEvent {
{
public static TikTokConnectingEvent of() {
return new TikTokConnectingEvent();
}
} }

View File

@@ -32,8 +32,7 @@ import lombok.Value;
@Value @Value
@EventMeta(eventType = EventType.Message) @EventMeta(eventType = EventType.Message)
public class TikTokFollowEvent extends TikTokHeaderEvent public class TikTokFollowEvent extends TikTokHeaderEvent {
{
User user; User user;
int totalFollowers; int totalFollowers;
@@ -43,12 +42,12 @@ public class TikTokFollowEvent extends TikTokHeaderEvent
totalFollowers = msg.getFollowCount(); totalFollowers = msg.getFollowCount();
} }
public static TikTokFollowEvent of(String userName) public static TikTokFollowEvent of(String userName) {
{
return new TikTokFollowEvent(WebcastSocialMessage.newBuilder() return new TikTokFollowEvent(WebcastSocialMessage.newBuilder()
.setUser(io.github.jwdeveloper.tiktok.messages.data.User.newBuilder() .setUser(io.github.jwdeveloper.tiktok.messages.data.User.newBuilder()
.setNickname(userName) .setDisplayId(userName)
.build()) .setNickname(userName)
.build()); .build())
.build());
} }
} }

View File

@@ -53,6 +53,7 @@ public class TikTokJoinEvent extends TikTokHeaderEvent {
{ {
return new TikTokJoinEvent(WebcastMemberMessage.newBuilder() return new TikTokJoinEvent(WebcastMemberMessage.newBuilder()
.setUser(io.github.jwdeveloper.tiktok.messages.data.User.newBuilder() .setUser(io.github.jwdeveloper.tiktok.messages.data.User.newBuilder()
.setDisplayId(userName)
.setNickname(userName) .setNickname(userName)
.build()) .build())
.build()); .build());

View File

@@ -62,6 +62,7 @@ public class TikTokLikeEvent extends TikTokHeaderEvent
.setCount(likes) .setCount(likes)
.setTotal(likes) .setTotal(likes)
.setUser(io.github.jwdeveloper.tiktok.messages.data.User.newBuilder() .setUser(io.github.jwdeveloper.tiktok.messages.data.User.newBuilder()
.setDisplayId(userName)
.setNickname(userName) .setNickname(userName)
.build()) .build())
.build()); .build());

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.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.messages.webcast.WebcastLikeMessage;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastSocialMessage; import io.github.jwdeveloper.tiktok.messages.webcast.WebcastSocialMessage;
import lombok.Getter; import lombok.Getter;
@@ -33,18 +34,27 @@ import lombok.Getter;
@Getter @Getter
@EventMeta(eventType = EventType.Message) @EventMeta(eventType = EventType.Message)
public class TikTokShareEvent extends TikTokHeaderEvent { public class TikTokShareEvent extends TikTokHeaderEvent {
private final User user; private final User user;
private final int totalShares; private final int totalShares;
public TikTokShareEvent(WebcastSocialMessage msg, Integer amount) { public TikTokShareEvent(WebcastSocialMessage msg, Integer amount) {
super(msg.getCommon()); super(msg.getCommon());
user = User.map(msg.getUser()); user = User.map(msg.getUser());
this.totalShares = amount; this.totalShares = amount;
} }
public TikTokShareEvent(WebcastSocialMessage msg) { public TikTokShareEvent(WebcastSocialMessage msg) {
super(msg.getCommon()); super(msg.getCommon());
user = User.map(msg.getUser()); user = User.map(msg.getUser());
totalShares = 1; totalShares = 1;
} }
public static TikTokShareEvent of(String userName, int shaders) {
return new TikTokShareEvent(WebcastSocialMessage.newBuilder()
.setUser(io.github.jwdeveloper.tiktok.messages.data.User.newBuilder()
.setDisplayId(userName)
.setNickname(userName)
.build())
.build(), shaders);
}
} }

View File

@@ -22,7 +22,12 @@
*/ */
package io.github.jwdeveloper.tiktok.data.models.battles; package io.github.jwdeveloper.tiktok.data.models.battles;
import lombok.Getter;
public abstract class Team { public abstract class Team {
/** Value >= 0 when finished otherwise -1 */
@Getter protected int totalPoints;
/** /**
* Provides a check for verifying if this team represents a 1v1 Team. * Provides a check for verifying if this team represents a 1v1 Team.
* @return true if this team is of type {@link Team1v1}, false otherwise. * @return true if this team is of type {@link Team1v1}, false otherwise.

View File

@@ -29,10 +29,7 @@ import lombok.Getter;
import java.util.*; import java.util.*;
@Getter @Getter
public class Team1v1 extends Team public class Team1v1 extends Team {
{
/** Value >= 0 when finished otherwise -1 */
private final int totalPoints;
private final int winStreak; private final int winStreak;
private final User host; private final User host;
private final List<Viewer> viewers; private final List<Viewer> viewers;

View File

@@ -30,9 +30,6 @@ import java.util.*;
@Getter @Getter
public class Team2v2 extends Team { public class Team2v2 extends Team {
/** Value >= 0 when finished otherwise -1 */
private final int totalPoints;
private final List<User> hosts; private final List<User> hosts;
private final List<Viewer> viewers; private final List<Viewer> viewers;

View File

@@ -53,4 +53,19 @@ public class ListUser
AUDIO, AUDIO,
VIDEO VIDEO
} }
@Override
public String toString() {
return "ListUser{" +
"user=" + user +
", linkType=" + linkType +
", linkMicId=" + linkMicId +
", linkStatus=" + linkStatus +
", modifyTime=" + modifyTime +
", linkerId=" + linkerId +
", userPosition=" + userPosition +
", silenceStatus=" + silenceStatus +
", roleType=" + roleType +
"}";
}
} }

View File

@@ -22,6 +22,7 @@
*/ */
package io.github.jwdeveloper.tiktok.data.requests; package io.github.jwdeveloper.tiktok.data.requests;
import io.github.jwdeveloper.tiktok.data.models.users.User;
import lombok.*; import lombok.*;
public class LiveUserData { public class LiveUserData {
@@ -44,6 +45,7 @@ public class LiveUserData {
private final UserStatus userStatus; private final UserStatus userStatus;
private final String roomId; private final String roomId;
private final long startTime; private final long startTime;
private final User user;
public boolean isLiveOnline() { public boolean isLiveOnline() {
return userStatus == LiveUserData.UserStatus.Live || userStatus == LiveUserData.UserStatus.LivePaused; return userStatus == LiveUserData.UserStatus.Live || userStatus == LiveUserData.UserStatus.LivePaused;

View File

@@ -82,9 +82,9 @@ public class LiveClientSettings {
/** /**
* Interval of time in milliseconds between pings to TikTok * Interval of time in milliseconds between pings to TikTok
* @apiNote Min: 250 (0.25 seconds), Default: 5000 (5 seconds) * @apiNote Min: 250 (0.25 seconds), Default: 10000 (10 seconds - TikTok Default)
*/ */
private long pingInterval = 5000; private long pingInterval = 10000;
/** Throw an exception on 18+ Age Restriction */ /** Throw an exception on 18+ Age Restriction */
private boolean throwOnAgeRestriction; private boolean throwOnAgeRestriction;

View File

@@ -25,16 +25,13 @@ package io.github.jwdeveloper.tiktok.listener;
import java.util.List; import java.util.List;
/** /**
* You can dynamically add or removing TikTokEventListener * Manage events listeners objects
*
* @see TikTokEventListener
*
*/ */
public interface ListenersManager public interface ListenersManager
{ {
List<TikTokEventListener> getListeners(); List<Object> getListeners();
void addListener(TikTokEventListener listener); void addListener(Object listener);
void removeListener(TikTokEventListener listener); void removeListener(Object listener);
} }

View File

@@ -38,9 +38,9 @@ import io.github.jwdeveloper.tiktok.live.LiveClient;
* <p>- 2 parameters of (LiveClient, Class extending TikTokEvent) * <p>- 2 parameters of (LiveClient, Class extending TikTokEvent)
* <pre> * <pre>
* {@code * {@code
* public static class CustomListener implements TikTokEventListener * public static class CustomListener
* { * {
* @TikTokEventObserver * @TikTokEventObserver
* public void onError(LiveClient liveClient, TikTokErrorEvent event) * public void onError(LiveClient liveClient, TikTokErrorEvent event)
* { * {
* System.out.println(event.getException().getMessage()); * System.out.println(event.getException().getMessage());
@@ -67,7 +67,7 @@ import io.github.jwdeveloper.tiktok.live.LiveClient;
* } * }
* </pre> * </pre>
*/ */
public interface TikTokEventListener @Deprecated(forRemoval = true, since = "1.8.1 (This interface is not longer needed, please remove it from your class) | Removing in 1.9.0")
{ public interface TikTokEventListener {
} }

View File

@@ -35,7 +35,7 @@ import io.github.jwdeveloper.tiktok.data.events.social.*;
import io.github.jwdeveloper.tiktok.data.events.websocket.*; import io.github.jwdeveloper.tiktok.data.events.websocket.*;
import io.github.jwdeveloper.tiktok.live.LiveClient; import io.github.jwdeveloper.tiktok.live.LiveClient;
public abstract class TikTokEventListenerBase implements TikTokEventListener public abstract class TikTokEventListenerBase
{ {
public void onUnhandledSocial(LiveClient client, TikTokUnhandledSocialEvent event) {} public void onUnhandledSocial(LiveClient client, TikTokUnhandledSocialEvent event) {}

View File

@@ -24,7 +24,6 @@ package io.github.jwdeveloper.tiktok.live;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent; 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 java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer; import java.util.function.Consumer;
@@ -81,9 +80,7 @@ public interface LiveClient {
LiveRoomInfo getRoomInfo(); LiveRoomInfo getRoomInfo();
/** /**
* Manage TikTokEventListener * Manage TikTokEvent Listeners
*
* @see TikTokEventListener
*/ */
ListenersManager getListenersManager(); ListenersManager getListenersManager();

View File

@@ -24,6 +24,7 @@ package io.github.jwdeveloper.tiktok.live.builder;
import io.github.jwdeveloper.tiktok.data.events.*; import io.github.jwdeveloper.tiktok.data.events.*;
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.control.TikTokConnectingEvent;
import io.github.jwdeveloper.tiktok.data.events.control.TikTokPreConnectionEvent; import io.github.jwdeveloper.tiktok.data.events.control.TikTokPreConnectionEvent;
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;
@@ -60,6 +61,18 @@ public interface EventsBuilder<T> {
return onEvent(TikTokEvent.class, action); return onEvent(TikTokEvent.class, action);
} }
/**
* As a first event after method `LiveClient::connect()` is performed
*
* @param action consumable action
* @return self instance
*/
default T onConnecting(EventConsumer<TikTokConnectingEvent> action)
{
return onEvent(TikTokConnectingEvent.class, action);
}
/** /**
* Invoked when information about room (live) got updated such as viewer count, etc.. * Invoked when information about room (live) got updated such as viewer count, etc..
* *

View File

@@ -24,7 +24,6 @@ package io.github.jwdeveloper.tiktok.live.builder;
import io.github.jwdeveloper.dependance.implementation.DependanceContainerBuilder; import io.github.jwdeveloper.dependance.implementation.DependanceContainerBuilder;
import io.github.jwdeveloper.tiktok.data.settings.LiveClientSettings; import io.github.jwdeveloper.tiktok.data.settings.LiveClientSettings;
import io.github.jwdeveloper.tiktok.listener.TikTokEventListener;
import io.github.jwdeveloper.tiktok.live.LiveClient; import io.github.jwdeveloper.tiktok.live.LiveClient;
import io.github.jwdeveloper.tiktok.mappers.LiveMapper; import io.github.jwdeveloper.tiktok.mappers.LiveMapper;
@@ -33,40 +32,42 @@ import java.util.function.Consumer;
public interface LiveClientBuilder extends EventsBuilder<LiveClientBuilder> { public interface LiveClientBuilder extends EventsBuilder<LiveClientBuilder> {
/** /**
* This method is triggered after default mappings are registered * This method is triggered after default mappings are registered
* It could be used to OVERRIDE behaviour of mappings and implement custom behaviour * It could be used to OVERRIDE behaviour of mappings and implement custom behaviour
* * <p>
* Be aware if for example you override WebcastGiftEvent, onGiftEvent() will not be working * Be aware if for example you override WebcastGiftEvent, onGiftEvent() will not be working
* *
* @param onCustomMappings lambda method * @param onCustomMappings lambda method
* @return * @return
*/ */
LiveClientBuilder onMapping(Consumer<LiveMapper> onCustomMappings); LiveClientBuilder mappings(Consumer<LiveMapper> onCustomMappings);
@Deprecated(forRemoval = true, since = "1.8.2 use `mappings` method instead")
LiveClientBuilder onMappings(Consumer<LiveMapper> onCustomMappings);
/** /**
* Configuration of client settings * Configuration of client settings
* @see LiveClientSettings *
* @param onConfigure * @param onConfigure
* @return * @return
* @see LiveClientSettings
*/ */
LiveClientBuilder configure(Consumer<LiveClientSettings> onConfigure); LiveClientBuilder configure(Consumer<LiveClientSettings> onConfigure);
/** /**
* @see TikTokEventListener * Adds events listener class, its fancy way to register events without using lamda method
* Adding events listener class, its fancy way to register events without using lamda method *
* but actual method in class that implements TikTokEventListener
* @return * @return
*/ */
LiveClientBuilder addListener(TikTokEventListener listener); LiveClientBuilder addListener(Object listener);
/** /**
* Allows you to use own implementation of internal TikTokLive dependencies, * Allows you to use own implementation of internal TikTokLive dependencies,
* when the default implementation does not meet your needs * when the default implementation does not meet your needs
* *
*
* @param onCustomizeDependencies access to dependency container * @param onCustomizeDependencies access to dependency container
* @return * @return
*/ */
@@ -74,18 +75,19 @@ public interface LiveClientBuilder extends EventsBuilder<LiveClientBuilder> {
/** /**
* Builds new instance of the LiveClient * Builds new instance of the LiveClient
*
* @return LiveClient object * @return LiveClient object
*/ */
LiveClient build(); LiveClient build();
/** /**
* Builds new instance of the LiveClient and connects to live * Builds new instance of the LiveClient and connects to live
*
* @return LiveClient object * @return LiveClient object
*/ */
LiveClient buildAndConnect(); LiveClient buildAndConnect();
/** /**
*
* @return LiveClient object and connects to TikTok live asynchronously * @return LiveClient object and connects to TikTok live asynchronously
*/ */
CompletableFuture<LiveClient> buildAndConnectAsync(); CompletableFuture<LiveClient> buildAndConnectAsync();

View File

@@ -24,9 +24,8 @@ package io.github.jwdeveloper.tiktok.websocket;
import io.github.jwdeveloper.tiktok.data.requests.LiveConnectionData; 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;
public interface SocketClient { public interface LiveSocketClient {
void start(LiveConnectionData.Response webcastResponse, LiveClient tikTokLiveClient); void start(LiveConnectionData.Response webcastResponse, LiveClient tikTokLiveClient);
void stop(); void stop();
} }

View File

@@ -155,8 +155,9 @@ 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_UNPAUSED = 2; // Stream Unpaused by Host
STREAM_ENDED = 3; // Stream Ended by Host STREAM_ENDED = 3; // Stream Ended by Host
STREAM_SUSPENDED = 4; // Stream Ended by TikTok
} }
enum LinkLayerMessageType enum LinkLayerMessageType

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.8.0-Release</version> <version>1.8.7-Release</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@@ -26,7 +26,6 @@ import com.google.protobuf.ByteString;
import io.github.jwdeveloper.tiktok.data.events.*; import io.github.jwdeveloper.tiktok.data.events.*;
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.control.*; import io.github.jwdeveloper.tiktok.data.events.control.*;
import io.github.jwdeveloper.tiktok.data.events.http.TikTokRoomDataResponseEvent;
import io.github.jwdeveloper.tiktok.data.events.room.TikTokRoomInfoEvent; import io.github.jwdeveloper.tiktok.data.events.room.TikTokRoomInfoEvent;
import io.github.jwdeveloper.tiktok.data.requests.*; import io.github.jwdeveloper.tiktok.data.requests.*;
import io.github.jwdeveloper.tiktok.data.settings.LiveClientSettings; import io.github.jwdeveloper.tiktok.data.settings.LiveClientSettings;
@@ -36,7 +35,7 @@ import io.github.jwdeveloper.tiktok.listener.*;
import io.github.jwdeveloper.tiktok.live.*; import io.github.jwdeveloper.tiktok.live.*;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastResponse; import io.github.jwdeveloper.tiktok.messages.webcast.WebcastResponse;
import io.github.jwdeveloper.tiktok.models.ConnectionState; import io.github.jwdeveloper.tiktok.models.ConnectionState;
import io.github.jwdeveloper.tiktok.websocket.SocketClient; import io.github.jwdeveloper.tiktok.websocket.LiveSocketClient;
import lombok.Getter; import lombok.Getter;
import java.util.Base64; import java.util.Base64;
@@ -49,7 +48,7 @@ public class TikTokLiveClient implements LiveClient
{ {
private final TikTokRoomInfo roomInfo; private final TikTokRoomInfo roomInfo;
private final LiveHttpClient httpClient; private final LiveHttpClient httpClient;
private final SocketClient webSocketClient; private final LiveSocketClient webSocketClient;
private final LiveEventsHandler tikTokEventHandler; private final LiveEventsHandler tikTokEventHandler;
private final LiveClientSettings clientSettings; private final LiveClientSettings clientSettings;
private final ListenersManager listenersManager; private final ListenersManager listenersManager;
@@ -62,7 +61,7 @@ public class TikTokLiveClient implements LiveClient
GiftsManager giftsManager, GiftsManager giftsManager,
TikTokRoomInfo tikTokLiveMeta, TikTokRoomInfo tikTokLiveMeta,
LiveHttpClient tiktokHttpClient, LiveHttpClient tiktokHttpClient,
SocketClient webSocketClient, LiveSocketClient webSocketClient,
LiveEventsHandler tikTokEventHandler, LiveEventsHandler tikTokEventHandler,
LiveClientSettings clientSettings, LiveClientSettings clientSettings,
ListenersManager listenersManager, ListenersManager listenersManager,
@@ -78,17 +77,6 @@ public class TikTokLiveClient implements LiveClient
this.logger = logger; this.logger = logger;
} }
public void connectAsync(Consumer<LiveClient> onConnection) {
connectAsync().thenAccept(onConnection);
}
public CompletableFuture<LiveClient> connectAsync() {
return CompletableFuture.supplyAsync(() -> {
connect();
return this;
});
}
public void connect() { public void connect() {
try { try {
tryConnect(); tryConnect();
@@ -100,8 +88,7 @@ public class TikTokLiveClient implements LiveClient
if (e instanceof TikTokLiveOfflineHostException && clientSettings.isRetryOnConnectionFailure()) { if (e instanceof TikTokLiveOfflineHostException && clientSettings.isRetryOnConnectionFailure()) {
try { try {
Thread.sleep(clientSettings.getRetryConnectionTimeout().toMillis()); Thread.sleep(clientSettings.getRetryConnectionTimeout().toMillis());
} catch (Exception ignored) { } catch (Exception ignored) {}
}
logger.info("Reconnecting"); logger.info("Reconnecting");
tikTokEventHandler.publish(this, new TikTokReconnectingEvent()); tikTokEventHandler.publish(this, new TikTokReconnectingEvent());
this.connect(); this.connect();
@@ -144,8 +131,6 @@ public class TikTokLiveClient implements LiveClient
if (liveData.getLiveStatus() == LiveData.LiveStatus.HostOffline) if (liveData.getLiveStatus() == LiveData.LiveStatus.HostOffline)
throw new TikTokLiveOfflineHostException("LiveStream for " + roomInfo.getHostName() + " not found, is the Host offline?"); throw new TikTokLiveOfflineHostException("LiveStream for " + roomInfo.getHostName() + " not found, is the Host offline?");
tikTokEventHandler.publish(this, new TikTokRoomDataResponseEvent(liveData));
roomInfo.setTitle(liveData.getTitle()); roomInfo.setTitle(liveData.getTitle());
roomInfo.setViewersCount(liveData.getViewers()); roomInfo.setViewersCount(liveData.getViewers());
roomInfo.setTotalViewersCount(liveData.getTotalViewers()); roomInfo.setTotalViewersCount(liveData.getTotalViewers());
@@ -199,4 +184,15 @@ public class TikTokLiveClient implements LiveClient
messageHandler.handleSingleMessage(this, message); messageHandler.handleSingleMessage(this, message);
} }
public void connectAsync(Consumer<LiveClient> onConnection) {
connectAsync().thenAccept(onConnection);
}
public CompletableFuture<LiveClient> connectAsync() {
return CompletableFuture.supplyAsync(() -> {
connect();
return this;
});
}
} }

View File

@@ -52,7 +52,7 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder {
protected final LiveClientSettings clientSettings; protected final LiveClientSettings clientSettings;
protected final LiveEventsHandler eventHandler; protected final LiveEventsHandler eventHandler;
protected final List<TikTokEventListener> listeners; protected final List<Object> listeners;
protected final List<Consumer<LiveMapper>> onCustomMappings; protected final List<Consumer<LiveMapper>> onCustomMappings;
protected final List<Consumer<DependanceContainerBuilder>> onCustomDependencies; protected final List<Consumer<DependanceContainerBuilder>> onCustomDependencies;
@@ -65,17 +65,23 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder {
this.onCustomDependencies = new ArrayList<>(); this.onCustomDependencies = new ArrayList<>();
} }
public LiveClientBuilder onMapping(Consumer<LiveMapper> consumer) { public LiveClientBuilder mappings(Consumer<LiveMapper> consumer) {
this.onCustomMappings.add(consumer); this.onCustomMappings.add(consumer);
return this; return this;
} }
@Override
public LiveClientBuilder onMappings(Consumer<LiveMapper> onCustomMappings) {
mappings(onCustomMappings);
return this;
}
public TikTokLiveClientBuilder configure(Consumer<LiveClientSettings> onConfigure) { public TikTokLiveClientBuilder configure(Consumer<LiveClientSettings> onConfigure) {
onConfigure.accept(clientSettings); onConfigure.accept(clientSettings);
return this; return this;
} }
public TikTokLiveClientBuilder addListener(TikTokEventListener listener) { public TikTokLiveClientBuilder addListener(Object listener) {
if (listener != null) if (listener != null)
listeners.add(listener); listeners.add(listener);
return this; return this;
@@ -97,8 +103,9 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder {
if (clientSettings.getHostName().startsWith("@")) if (clientSettings.getHostName().startsWith("@"))
clientSettings.setHostName(clientSettings.getHostName().substring(1)); clientSettings.setHostName(clientSettings.getHostName().substring(1));
//TODO 250 Magic number
if (clientSettings.getPingInterval() < 250) if (clientSettings.getPingInterval() < 250)
throw new TikTokLiveException("Minimum allowed ping interval is 250 millseconds"); throw new TikTokLiveException("Minimum allowed ping interval is 250 milliseconds");
var httpSettings = clientSettings.getHttpSettings(); var httpSettings = clientSettings.getHttpSettings();
httpSettings.getParams().put("app_language", clientSettings.getClientLanguage()); httpSettings.getParams().put("app_language", clientSettings.getClientLanguage());
@@ -124,20 +131,19 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder {
//messages //messages
dependance.registerSingleton(LiveEventsHandler.class, eventHandler); dependance.registerSingleton(LiveEventsHandler.class, eventHandler);
dependance.registerSingleton(LiveMessagesHandler.class,TikTokLiveMessageHandler.class); dependance.registerSingleton(LiveMessagesHandler.class, TikTokLiveMessageHandler.class);
//listeners //listeners
dependance.registerSingletonList(TikTokEventListener.class, (e) -> listeners);
dependance.registerSingleton(ListenersManager.class, TikTokListenersManager.class); dependance.registerSingleton(ListenersManager.class, TikTokListenersManager.class);
//networking //networking
dependance.registerSingleton(HttpClientFactory.class); dependance.registerSingleton(HttpClientFactory.class);
dependance.registerSingleton(TikTokWebSocketPingingTask.class); dependance.registerSingleton(WebSocketHeartbeatTask.class);
if (clientSettings.isOffline()) { if (clientSettings.isOffline()) {
dependance.registerSingleton(SocketClient.class, TikTokWebSocketOfflineClient.class); dependance.registerSingleton(LiveSocketClient.class, TikTokWebSocketOfflineClient.class);
dependance.registerSingleton(LiveHttpClient.class, TikTokLiveHttpOfflineClient.class); dependance.registerSingleton(LiveHttpClient.class, TikTokLiveHttpOfflineClient.class);
} else { } else {
dependance.registerSingleton(SocketClient.class, TikTokWebSocketClient.class); dependance.registerSingleton(LiveSocketClient.class, TikTokWebSocketClient.class);
dependance.registerSingleton(LiveHttpClient.class, TikTokLiveHttpClient.class); dependance.registerSingleton(LiveHttpClient.class, TikTokLiveHttpClient.class);
} }
@@ -176,8 +182,10 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder {
dependance.registerSingleton(LiveClient.class, TikTokLiveClient.class); dependance.registerSingleton(LiveClient.class, TikTokLiveClient.class);
onCustomDependencies.forEach(action -> action.accept(dependance)); onCustomDependencies.forEach(action -> action.accept(dependance));
var container = dependance.build(); var container = dependance.build();
var listenerManager = container.find(ListenersManager.class);
listeners.forEach(listenerManager::addListener);
return container.find(LiveClient.class); return container.find(LiveClient.class);
} }

View File

@@ -107,6 +107,7 @@ public class TikTokLiveHttpClient implements LiveHttpClient
return getGiftsData(); return getGiftsData();
} }
@Deprecated(since = "1.8.6", forRemoval = true)
public GiftsData.Response getGiftsData() { public GiftsData.Response getGiftsData() {
var result = httpFactory.client(TIKTOK_GIFTS_URL) var result = httpFactory.client(TIKTOK_GIFTS_URL)
.build() .build()
@@ -136,7 +137,7 @@ public class TikTokLiveHttpClient implements LiveHttpClient
var url = TIKTOK_URL_WEB + "api-live/user/room"; var url = TIKTOK_URL_WEB + "api-live/user/room";
var result = httpFactory.client(url) var result = httpFactory.client(url)
.withParam("uniqueId", request.getUserName()) .withParam("uniqueId", request.getUserName())
.withParam("sourceType", "54") .withParam("sourceType", "54") //MAGIC NUMBER, WHAT 54 means?
.build() .build()
.toJsonResponse(); .toJsonResponse();
@@ -203,7 +204,7 @@ public class TikTokLiveHttpClient implements LiveHttpClient
} }
} }
private ActionResult<HttpResponse<byte[]>> getStartingPayload(LiveConnectionData.Request request) { protected ActionResult<HttpResponse<byte[]>> getStartingPayload(LiveConnectionData.Request request) {
var proxyClientSettings = clientSettings.getHttpSettings().getProxyClientSettings(); var proxyClientSettings = clientSettings.getHttpSettings().getProxyClientSettings();
if (proxyClientSettings.isEnabled()) { if (proxyClientSettings.isEnabled()) {
while (proxyClientSettings.hasNext()) { while (proxyClientSettings.hasNext()) {
@@ -215,10 +216,10 @@ public class TikTokLiveHttpClient implements LiveHttpClient
return getByteResponse(request.getRoomId()); return getByteResponse(request.getRoomId());
} }
private ActionResult<HttpResponse<byte[]>> getByteResponse(String room_id) { protected ActionResult<HttpResponse<byte[]>> getByteResponse(String room_id) {
HttpClientBuilder builder = httpFactory.client(TIKTOK_SIGN_API) HttpClientBuilder builder = httpFactory.client(TIKTOK_SIGN_API)
.withParam("client", "ttlive-java") .withParam("client", "ttlive-java")
.withParam("uuc", "1") .withParam("uuc", "1") //MAGIC NUMBER!
.withParam("room_id", room_id); .withParam("room_id", room_id);
if (clientSettings.getApiKey() != null) if (clientSettings.getApiKey() != null)

View File

@@ -47,7 +47,7 @@ public class TikTokLiveHttpOfflineClient implements LiveHttpClient {
@Override @Override
public LiveUserData.Response fetchLiveUserData(LiveUserData.Request request) { public LiveUserData.Response fetchLiveUserData(LiveUserData.Request request) {
return new LiveUserData.Response("", LiveUserData.UserStatus.Live, "offline_room_id", 0); return new LiveUserData.Response("", LiveUserData.UserStatus.Live, "offline_room_id", 0, null);
} }
@Override @Override

View File

@@ -27,6 +27,7 @@ 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.requests.GiftsData; import io.github.jwdeveloper.tiktok.data.requests.GiftsData;
import java.util.ArrayList;
import java.util.List; import java.util.List;
public class GiftsDataMapper { public class GiftsDataMapper {
@@ -56,9 +57,13 @@ public class GiftsDataMapper {
var parsedJson = JsonParser.parseString(json); var parsedJson = JsonParser.parseString(json);
var jsonObject = parsedJson.getAsJsonObject(); var jsonObject = parsedJson.getAsJsonObject();
if (jsonObject.get("data") instanceof JsonObject data && data.get("gifts") instanceof JsonArray giftArray) { if (jsonObject.get("data") instanceof JsonObject data && data.get("gifts") instanceof JsonArray giftArray) {
var gifts = giftArray.asList().parallelStream() var gifts = new ArrayList<Gift>();
.map(this::mapSingleRoomGift)
.toList(); for(int i = 0; i < giftArray.size(); i++) {
JsonElement element = giftArray.get(i);
Gift gift = mapSingleRoomGift(element);
gifts.add(gift);
}
return new GiftsData.Response(json, gifts); return new GiftsData.Response(json, gifts);
} }

View File

@@ -142,7 +142,7 @@ public class LiveDataMapper {
var link = pictureElement.getAsJsonArray("url_list").get(1).getAsString(); var link = pictureElement.getAsJsonArray("url_list").get(1).getAsString();
var picture = new Picture(link); var picture = new Picture(link);
var user = new User(id, name, profileName, picture, followers, followingCount, new ArrayList<>()); var user = new User(id, name, profileName, picture, followingCount, followers, new ArrayList<>());
user.addAttribute(UserAttribute.LiveHost); user.addAttribute(UserAttribute.LiveHost);
return user; return user;
} }

View File

@@ -23,9 +23,12 @@
package io.github.jwdeveloper.tiktok.http.mappers; package io.github.jwdeveloper.tiktok.http.mappers;
import com.google.gson.*; import com.google.gson.*;
import io.github.jwdeveloper.tiktok.data.models.Picture;
import io.github.jwdeveloper.tiktok.data.models.users.User;
import io.github.jwdeveloper.tiktok.data.requests.LiveUserData; import io.github.jwdeveloper.tiktok.data.requests.LiveUserData;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveRequestException; import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveRequestException;
import java.util.List;
import java.util.logging.Logger; import java.util.logging.Logger;
public class LiveUserDataMapper public class LiveUserDataMapper
@@ -40,17 +43,18 @@ public class LiveUserDataMapper
throw new TikTokLiveRequestException("fetchRoomIdFromTiktokApi -> Unable to fetch roomID, contact the developer"); throw new TikTokLiveRequestException("fetchRoomIdFromTiktokApi -> Unable to fetch roomID, contact the developer");
} }
if (message.equals("user_not_found")) { if (message.equals("user_not_found")) {
return new LiveUserData.Response(json, LiveUserData.UserStatus.NotFound, "", -1); return new LiveUserData.Response(json, LiveUserData.UserStatus.NotFound, "", -1, null);
} }
//live -> status 2 //live -> status 2
//live paused -> 3 //live paused -> 3
//not live -> status 4 //not live -> status 4
var element = jsonObject.get("data"); var element = jsonObject.get("data");
if (element.isJsonNull()) { if (element.isJsonNull()) {
return new LiveUserData.Response(json, LiveUserData.UserStatus.NotFound, "", -1); return new LiveUserData.Response(json, LiveUserData.UserStatus.NotFound, "", -1, null);
} }
var data = element.getAsJsonObject(); var data = element.getAsJsonObject();
var user = data.getAsJsonObject("user"); var user = data.getAsJsonObject("user");
var stats = data.getAsJsonObject("stats");
var roomId = user.get("roomId").getAsString(); var roomId = user.get("roomId").getAsString();
var status = user.get("status").getAsInt(); var status = user.get("status").getAsInt();
@@ -64,10 +68,19 @@ public class LiveUserDataMapper
default -> LiveUserData.UserStatus.NotFound; default -> LiveUserData.UserStatus.NotFound;
}; };
return new LiveUserData.Response(json, statusEnum, roomId, startTime); User foundUser = new User(
Long.parseLong(user.get("id").getAsString()),
user.get("uniqueId").getAsString(),
user.get("nickname").getAsString(),
new Picture(user.get("avatarLarger").getAsString()),
stats.get("followingCount").getAsLong(),
stats.get("followerCount").getAsLong(),
List.of());
return new LiveUserData.Response(json, statusEnum, roomId, startTime, foundUser);
} catch (JsonSyntaxException | IllegalStateException e) { } catch (JsonSyntaxException | IllegalStateException e) {
logger.warning("Malformed Json: '"+json+"' - Error Message: "+e.getMessage()); logger.warning("Malformed Json: '"+json+"' - Error Message: "+e.getMessage());
return new LiveUserData.Response(json, LiveUserData.UserStatus.NotFound, "", -1); return new LiveUserData.Response(json, LiveUserData.UserStatus.NotFound, "", -1, null);
} }
} }
} }

View File

@@ -22,19 +22,26 @@
*/ */
package io.github.jwdeveloper.tiktok.listener; package io.github.jwdeveloper.tiktok.listener;
import io.github.jwdeveloper.tiktok.annotations.Priority;
import io.github.jwdeveloper.tiktok.live.builder.EventConsumer; import io.github.jwdeveloper.tiktok.live.builder.EventConsumer;
import lombok.Data;
import lombok.Value; import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
@Value @Data
public class ListenerBindingModel public class ListenerMethodInfo {
{ private Object listener;
TikTokEventListener listener; private Class eventType;
Map<Class<?>, List<EventConsumer<?>>> events; private Method method;
private Priority priority;
private boolean async;
private EventConsumer action = (a, b) -> {
};
} }

View File

@@ -23,104 +23,135 @@
package io.github.jwdeveloper.tiktok.listener; package io.github.jwdeveloper.tiktok.listener;
import io.github.jwdeveloper.tiktok.TikTokLiveEventHandler; import io.github.jwdeveloper.dependance.api.DependanceContainer;
import io.github.jwdeveloper.tiktok.annotations.TikTokEventObserver; import io.github.jwdeveloper.tiktok.annotations.TikTokEventObserver;
import io.github.jwdeveloper.tiktok.data.events.TikTokErrorEvent;
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.live.LiveClient;
import io.github.jwdeveloper.tiktok.live.LiveEventsHandler; import io.github.jwdeveloper.tiktok.live.LiveEventsHandler;
import io.github.jwdeveloper.tiktok.live.builder.EventConsumer; import io.github.jwdeveloper.tiktok.live.builder.EventConsumer;
import java.util.ArrayList; import java.lang.reflect.Method;
import java.util.Arrays; import java.util.*;
import java.util.HashMap; import java.util.concurrent.ExecutorService;
import java.util.List; import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicReference;
public class TikTokListenersManager implements ListenersManager { public class TikTokListenersManager implements ListenersManager {
private final LiveEventsHandler eventObserver;
private final List<ListenerBindingModel> bindingModels;
public TikTokListenersManager(List<TikTokEventListener> listeners, LiveEventsHandler tikTokEventHandler) { private final Map<Object, List<ListenerMethodInfo>> listeners;
this.eventObserver = tikTokEventHandler; private final LiveEventsHandler eventsHandler;
this.bindingModels = new ArrayList<>(listeners.size()); private final ExecutorService executorService;
for (var listener : listeners) { private final DependanceContainer dependanceContainer;
addListener(listener);
}
public TikTokListenersManager(LiveEventsHandler tikTokEventHandler,
DependanceContainer dependanceContainer) {
this.eventsHandler = tikTokEventHandler;
this.dependanceContainer = dependanceContainer;
this.listeners = new HashMap<>();
executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
} }
@Override @Override
public List<TikTokEventListener> getListeners() { public List<Object> getListeners() {
return bindingModels.stream().map(ListenerBindingModel::getListener).toList(); return listeners.keySet().stream().toList();
} }
@Override @Override
public void addListener(TikTokEventListener listener) { public void addListener(Object listener) {
var alreadyExists = bindingModels.stream().filter(e -> e.getListener() == listener).findAny(); if (listeners.containsKey(listener)) {
if (alreadyExists.isPresent()) {
throw new TikTokLiveException("Listener " + listener.getClass() + " has already been registered"); throw new TikTokLiveException("Listener " + listener.getClass() + " has already been registered");
} }
var bindingModel = bindToEvents(listener);
for (var eventEntrySet : bindingModel.getEvents().entrySet()) { var methodsInfo = getMethodsInfo(listener);
var eventType = eventEntrySet.getKey(); for (var methodInfo : methodsInfo) {
for (var methods : eventEntrySet.getValue()) { eventsHandler.subscribe(methodInfo.getEventType(), methodInfo.getAction());
eventObserver.subscribe(eventType, methods);
}
} }
bindingModels.add(bindingModel); listeners.put(listener, methodsInfo);
} }
@Override @Override
public void removeListener(TikTokEventListener listener) { public void removeListener(Object listener) {
var optional = bindingModels.stream().filter(e -> e.getListener() == listener).findAny(); if (!listeners.containsKey(listener)) {
if (optional.isEmpty()) {
return; return;
} }
var methodsInfo = listeners.get(listener);
var bindingModel = optional.get(); for (var methodInfo : methodsInfo) {
eventsHandler.unsubscribe(methodInfo.getEventType(), methodInfo.getAction());
for (var eventEntrySet : bindingModel.getEvents().entrySet()) {
var eventType = eventEntrySet.getKey();
for (var methods : eventEntrySet.getValue()) {
eventObserver.unsubscribe(eventType, methods);
}
} }
bindingModels.remove(optional.get()); listeners.remove(listener);
} }
private ListenerBindingModel bindToEvents(TikTokEventListener listener) { private List<ListenerMethodInfo> getMethodsInfo(Object listener) {
return Arrays.stream(listener.getClass().getDeclaredMethods())
.filter(e -> e.isAnnotationPresent(TikTokEventObserver.class))
.filter(e -> e.getParameterCount() >= 1)
.map(method -> getSingleMethodInfo(listener, method))
.sorted(Comparator.comparingInt(a -> a.getPriority().value))
.toList();
}
var clazz = listener.getClass(); private ListenerMethodInfo getSingleMethodInfo(Object listener, Method method) {
var methods = Arrays.stream(clazz.getDeclaredMethods()).filter(m ->
m.getParameterCount() == 2 &&
m.isAnnotationPresent(TikTokEventObserver.class)).toList();
var eventsMap = new HashMap<Class<?>, List<EventConsumer<?>>>();
for (var method : methods) {
var liveclientClass = method.getParameterTypes()[0];
var eventClass = method.getParameterTypes()[1];
if (!LiveClient.class.isAssignableFrom(liveclientClass) && !liveclientClass.equals(LiveClient.class)) { method.setAccessible(true);
throw new TikTokEventListenerMethodException("Method " + method.getName() + "() 1st parameter must be instance of " + LiveClient.class.getName() var annotation = method.getAnnotation(TikTokEventObserver.class);
+ " | Invalid parameter class: "+liveclientClass.getName()); var tiktokEventType = Arrays.stream(method.getParameterTypes())
} .filter(TikTokEvent.class::isAssignableFrom)
.findFirst()
.orElseThrow(() -> new TikTokEventListenerMethodException("Method " + method.getName() + "() must have only one parameter that inherits from class " + TikTokEvent.class.getName()));
if (!TikTokEvent.class.isAssignableFrom(eventClass) && !eventClass.equals(TikTokEvent.class)) { var info = new ListenerMethodInfo();
throw new TikTokEventListenerMethodException("Method " + method.getName() + "() 2nd parameter must be instance of " + TikTokEvent.class.getName() info.setListener(listener);
+ " | Invalid parameter class: "+eventClass.getName()); info.setAsync(annotation.async());
} info.setPriority(annotation.priority());
info.setEventType(tiktokEventType);
info.setAction(createAction(listener, method, tiktokEventType));
EventConsumer eventMethodRef = (liveClient, event) -> if (info.isAsync()) {
var action = info.getAction();
info.setAction((liveClient, event) ->
{ {
try { executorService.submit(() ->
method.setAccessible(true); {
method.invoke(listener, liveClient, event); action.onEvent(liveClient, event);
} catch (Exception e) { });
throw new TikTokEventListenerMethodException(e); });
}
};
eventsMap.computeIfAbsent(eventClass, (a) -> new ArrayList<>()).add(eventMethodRef);
} }
return new ListenerBindingModel(listener, eventsMap); return info;
}
//I know, implementation of this might look complicated
private EventConsumer createAction(Object listener, Method method, Class tiktokEventType) {
AtomicReference<Object> eventObjectRef = new AtomicReference<>();
var methodContainer = dependanceContainer.createChildContainer()
.configure(configuration ->
{
//Modifying container, so it returns TikTokEvent object instance,
//when TikTokEvent type is encountered in the methods parameters
configuration.onInjection(injectionEvent ->
{
if (injectionEvent.input().isAssignableFrom(tiktokEventType)) {
return eventObjectRef.get();
}
return injectionEvent.output();
});
})
.build();
return (liveClient, event) ->
{
try {
eventObjectRef.set(event);
//Creating list of input objects based on method parameters
//Objects are received from container
var parameters = methodContainer.resolveParameters(method);
method.invoke(listener, parameters);
} catch (Exception e) {
eventsHandler.publish(liveClient, new TikTokErrorEvent(new TikTokEventListenerMethodException(e)));
}
};
} }
} }

View File

@@ -53,7 +53,7 @@ public class TikTokGenericEventMapper {
private final Map<Class<?>, Method> methodCache; private final Map<Class<?>, Method> methodCache;
private final Map<TypePair, Constructor<?>> constructorCache; private final Map<TypePair, Constructor<?>> constructorCache;
private static final String PARSE_FIELD = "parseFrom";
public TikTokGenericEventMapper() { public TikTokGenericEventMapper() {
this.methodCache = new HashMap<>(); this.methodCache = new HashMap<>();
this.constructorCache = new HashMap<>(); this.constructorCache = new HashMap<>();
@@ -75,7 +75,7 @@ public class TikTokGenericEventMapper {
public Method getParsingMethod(Class<?> input) throws RuntimeException { public Method getParsingMethod(Class<?> input) throws RuntimeException {
return methodCache.computeIfAbsent(input, aClass -> { return methodCache.computeIfAbsent(input, aClass -> {
try { try {
return aClass.getDeclaredMethod("parseFrom", byte[].class); return aClass.getDeclaredMethod(PARSE_FIELD, byte[].class);
} catch (NoSuchMethodException e) { } catch (NoSuchMethodException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }

View File

@@ -34,11 +34,12 @@ public class TikTokLiveMapper implements LiveMapper {
private final Map<String, TikTokLiveMapperModel> mappers; private final Map<String, TikTokLiveMapperModel> mappers;
private final LiveMapperHelper mapperUtils; private final LiveMapperHelper mapperUtils;
private final TikTokLiveMapperModel globalMapperModel; private final TikTokLiveMapperModel globalMapperModel;
private static final String GLOBAL_MESSAGE = "GLOBAL MESSAGE";
public TikTokLiveMapper(LiveMapperHelper mapperUtils) { public TikTokLiveMapper(LiveMapperHelper mapperUtils) {
this.mappers = new HashMap<>(); this.mappers = new HashMap<>();
this.mapperUtils = mapperUtils; this.mapperUtils = mapperUtils;
this.globalMapperModel = new TikTokLiveMapperModel("any message"); this.globalMapperModel = new TikTokLiveMapperModel(GLOBAL_MESSAGE);
} }
@Override @Override

View File

@@ -29,6 +29,9 @@ import io.github.jwdeveloper.tiktok.utils.ProtoBufferObject;
import io.github.jwdeveloper.tiktok.utils.ProtocolUtils; import io.github.jwdeveloper.tiktok.utils.ProtocolUtils;
public class TikTokLiveMapperHelper implements LiveMapperHelper { public class TikTokLiveMapperHelper implements LiveMapperHelper {
private static final String PACKAGE_PREFIX = "io.github.jwdeveloper.tiktok.messages.webcast.";
private final TikTokGenericEventMapper genericMapper; private final TikTokGenericEventMapper genericMapper;
public TikTokLiveMapperHelper(TikTokGenericEventMapper genericMapper) { public TikTokLiveMapperHelper(TikTokGenericEventMapper genericMapper) {
@@ -39,6 +42,7 @@ public class TikTokLiveMapperHelper implements LiveMapperHelper {
public <T extends GeneratedMessageV3> T bytesToWebcastObject(byte[] bytes, Class<T> messageClass) { public <T extends GeneratedMessageV3> T bytesToWebcastObject(byte[] bytes, Class<T> messageClass) {
try { try {
var parsingMethod = genericMapper.getParsingMethod(messageClass); var parsingMethod = genericMapper.getParsingMethod(messageClass);
//NULL is passed, since Parsing method is Static
var sourceObject = parsingMethod.invoke(null, bytes); var sourceObject = parsingMethod.invoke(null, bytes);
return (T) sourceObject; return (T) sourceObject;
} catch (Exception e) { } catch (Exception e) {
@@ -49,7 +53,7 @@ public class TikTokLiveMapperHelper implements LiveMapperHelper {
@Override @Override
public Object bytesToWebcastObject(byte[] bytes, String messageName) { public Object bytesToWebcastObject(byte[] bytes, String messageName) {
try { try {
var packageName = "io.github.jwdeveloper.tiktok.messages.webcast." + messageName; var packageName = PACKAGE_PREFIX + messageName;
var clazz = Class.forName(packageName); var clazz = Class.forName(packageName);
return bytesToWebcastObject(bytes, (Class<? extends GeneratedMessageV3>) clazz); return bytesToWebcastObject(bytes, (Class<? extends GeneratedMessageV3>) clazz);
} catch (Exception e) { } catch (Exception e) {
@@ -60,7 +64,7 @@ public class TikTokLiveMapperHelper implements LiveMapperHelper {
@Override @Override
public boolean isMessageHasProtoClass(String messageName) { public boolean isMessageHasProtoClass(String messageName) {
try { try {
var packageName = "io.github.jwdeveloper.tiktok.messages.webcast." + messageName; var packageName = PACKAGE_PREFIX + messageName;
Class.forName(packageName); Class.forName(packageName);
return true; return true;
} catch (Exception e) { } catch (Exception e) {

View File

@@ -46,7 +46,7 @@ public class TikTokCommonEventHandler
var message = WebcastControlMessage.parseFrom(msg); var message = WebcastControlMessage.parseFrom(msg);
return switch (message.getAction()) { return switch (message.getAction()) {
case STREAM_PAUSED -> new TikTokLivePausedEvent(); case STREAM_PAUSED -> new TikTokLivePausedEvent();
case STREAM_ENDED -> new TikTokLiveEndedEvent(); case STREAM_ENDED, STREAM_SUSPENDED -> new TikTokLiveEndedEvent();
case STREAM_UNPAUSED -> new TikTokLiveUnpausedEvent(); case STREAM_UNPAUSED -> new TikTokLiveUnpausedEvent();
default -> new TikTokUnhandledControlEvent(message); default -> new TikTokUnhandledControlEvent(message);
}; };

View File

@@ -38,7 +38,6 @@ import java.util.*;
public class TikTokGiftEventHandler { public class TikTokGiftEventHandler {
private final Map<Long, WebcastGiftMessage> giftsMessages; private final Map<Long, WebcastGiftMessage> giftsMessages;
private final TikTokRoomInfo tikTokRoomInfo; private final TikTokRoomInfo tikTokRoomInfo;
private final GiftsManager giftsManager; private final GiftsManager giftsManager;
public TikTokGiftEventHandler(GiftsManager giftsManager, TikTokRoomInfo tikTokRoomInfo) { public TikTokGiftEventHandler(GiftsManager giftsManager, TikTokRoomInfo tikTokRoomInfo) {

View File

@@ -58,6 +58,7 @@ public class TikTokRoomInfoEventHandler {
@SneakyThrows @SneakyThrows
public TikTokEvent handleUserRanking(byte[] msg) { public TikTokEvent handleUserRanking(byte[] msg) {
var message = WebcastRoomUserSeqMessage.parseFrom(msg); var message = WebcastRoomUserSeqMessage.parseFrom(msg);
var currentViewers = (int) message.getTotal();
var totalUsers = message.getTotalUser(); var totalUsers = message.getTotalUser();
var userRanking = message.getRanksListList().stream().map(RankingUser::new) var userRanking = message.getRanksListList().stream().map(RankingUser::new)
.sorted((ru1, ru2) -> Integer.compare(ru2.getScore(), ru1.getScore())) .sorted((ru1, ru2) -> Integer.compare(ru2.getScore(), ru1.getScore()))
@@ -65,6 +66,7 @@ public class TikTokRoomInfoEventHandler {
return handleRoomInfo(tikTokRoomInfo -> return handleRoomInfo(tikTokRoomInfo ->
{ {
tikTokRoomInfo.setViewersCount(currentViewers);
tikTokRoomInfo.setTotalViewersCount(totalUsers); tikTokRoomInfo.setTotalViewersCount(totalUsers);
tikTokRoomInfo.updateRanking(userRanking); tikTokRoomInfo.updateRanking(userRanking);
}); });

View File

@@ -22,8 +22,6 @@
*/ */
package io.github.jwdeveloper.tiktok.websocket; package io.github.jwdeveloper.tiktok.websocket;
import io.github.jwdeveloper.dependance.injector.api.containers.Container;
import io.github.jwdeveloper.tiktok.*;
import io.github.jwdeveloper.tiktok.data.dto.ProxyData; import io.github.jwdeveloper.tiktok.data.dto.ProxyData;
import io.github.jwdeveloper.tiktok.data.requests.LiveConnectionData; import io.github.jwdeveloper.tiktok.data.requests.LiveConnectionData;
import io.github.jwdeveloper.tiktok.data.settings.*; import io.github.jwdeveloper.tiktok.data.settings.*;
@@ -38,11 +36,11 @@ import java.net.Proxy;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.util.HashMap; import java.util.HashMap;
public class TikTokWebSocketClient implements SocketClient { public class TikTokWebSocketClient implements LiveSocketClient {
private final LiveClientSettings clientSettings; private final LiveClientSettings clientSettings;
private final LiveMessagesHandler messageHandler; private final LiveMessagesHandler messageHandler;
private final LiveEventsHandler tikTokEventHandler; private final LiveEventsHandler tikTokEventHandler;
private final TikTokWebSocketPingingTask pingingTask; private final WebSocketHeartbeatTask heartbeatTask;
private WebSocketClient webSocketClient; private WebSocketClient webSocketClient;
private boolean isConnected; private boolean isConnected;
@@ -50,12 +48,12 @@ public class TikTokWebSocketClient implements SocketClient {
LiveClientSettings clientSettings, LiveClientSettings clientSettings,
LiveMessagesHandler messageHandler, LiveMessagesHandler messageHandler,
LiveEventsHandler tikTokEventHandler, LiveEventsHandler tikTokEventHandler,
TikTokWebSocketPingingTask pingingTask) WebSocketHeartbeatTask heartbeatTask)
{ {
this.clientSettings = clientSettings; this.clientSettings = clientSettings;
this.messageHandler = messageHandler; this.messageHandler = messageHandler;
this.tikTokEventHandler = tikTokEventHandler; this.tikTokEventHandler = tikTokEventHandler;
this.pingingTask = pingingTask; this.heartbeatTask = heartbeatTask;
isConnected = false; isConnected = false;
} }
@@ -86,7 +84,7 @@ public class TikTokWebSocketClient implements SocketClient {
private void connectDefault() { private void connectDefault() {
try { try {
webSocketClient.connect(); webSocketClient.connect();
pingingTask.run(webSocketClient, clientSettings.getPingInterval()); heartbeatTask.run(webSocketClient, clientSettings.getPingInterval());
isConnected = true; isConnected = true;
} catch (Exception e) { } catch (Exception e) {
isConnected = false; isConnected = false;
@@ -122,7 +120,7 @@ public class TikTokWebSocketClient implements SocketClient {
proxySettings.remove(); proxySettings.remove();
continue; continue;
} }
pingingTask.run(webSocketClient, clientSettings.getPingInterval()); heartbeatTask.run(webSocketClient, clientSettings.getPingInterval());
isConnected = true; isConnected = true;
break; break;
} }
@@ -143,7 +141,7 @@ public class TikTokWebSocketClient implements SocketClient {
public void stop() { public void stop() {
if (isConnected && webSocketClient != null && webSocketClient.isOpen()) { if (isConnected && webSocketClient != null && webSocketClient.isOpen()) {
webSocketClient.closeConnection(0, ""); webSocketClient.closeConnection(0, "");
pingingTask.stop(); heartbeatTask.stop();
} }
webSocketClient = null; webSocketClient = null;
isConnected = false; isConnected = false;

View File

@@ -23,7 +23,6 @@
package io.github.jwdeveloper.tiktok.websocket; package io.github.jwdeveloper.tiktok.websocket;
import com.google.protobuf.ByteString; import com.google.protobuf.ByteString;
import io.github.jwdeveloper.tiktok.*;
import io.github.jwdeveloper.tiktok.data.events.*; import io.github.jwdeveloper.tiktok.data.events.*;
import io.github.jwdeveloper.tiktok.exceptions.TikTokProtocolBufferException; import io.github.jwdeveloper.tiktok.exceptions.TikTokProtocolBufferException;
import io.github.jwdeveloper.tiktok.live.LiveClient; import io.github.jwdeveloper.tiktok.live.LiveClient;
@@ -40,9 +39,9 @@ import java.util.*;
public class TikTokWebSocketListener extends WebSocketClient { public class TikTokWebSocketListener extends WebSocketClient {
private final LiveMessagesHandler messageHandler; private final LiveMessagesHandler messagesHandler;
private final LiveEventsHandler tikTokEventHandler; private final LiveEventsHandler eventHandler;
private final LiveClient tikTokLiveClient; private final LiveClient liveClient;
public TikTokWebSocketListener(URI serverUri, public TikTokWebSocketListener(URI serverUri,
Map<String, String> httpHeaders, Map<String, String> httpHeaders,
@@ -51,9 +50,9 @@ public class TikTokWebSocketListener extends WebSocketClient {
LiveEventsHandler tikTokEventHandler, LiveEventsHandler tikTokEventHandler,
LiveClient tikTokLiveClient) { LiveClient tikTokLiveClient) {
super(serverUri, new Draft_6455(), httpHeaders, connectTimeout); super(serverUri, new Draft_6455(), httpHeaders, connectTimeout);
this.messageHandler = messageHandler; this.messagesHandler = messageHandler;
this.tikTokEventHandler = tikTokEventHandler; this.eventHandler = tikTokEventHandler;
this.tikTokLiveClient = tikTokLiveClient; this.liveClient = tikTokLiveClient;
} }
@Override @Override
@@ -61,7 +60,7 @@ public class TikTokWebSocketListener extends WebSocketClient {
try { try {
handleBinary(bytes.array()); handleBinary(bytes.array());
} catch (Exception e) { } catch (Exception e) {
tikTokEventHandler.publish(tikTokLiveClient, new TikTokErrorEvent(e)); eventHandler.publish(liveClient, new TikTokErrorEvent(e));
} }
if (isOpen()) { if (isOpen()) {
sendPing(); sendPing();
@@ -85,12 +84,12 @@ public class TikTokWebSocketListener extends WebSocketClient {
this.send(pushFrameBuilder.build().toByteArray()); this.send(pushFrameBuilder.build().toByteArray());
} }
} }
messageHandler.handle(tikTokLiveClient, webcastResponse); messagesHandler.handle(liveClient, webcastResponse);
} }
@Override @Override
public void onOpen(ServerHandshake serverHandshake) { public void onOpen(ServerHandshake serverHandshake) {
tikTokEventHandler.publish(tikTokLiveClient, new TikTokConnectedEvent()); eventHandler.publish(liveClient, new TikTokConnectedEvent());
if (isOpen()) { if (isOpen()) {
sendPing(); sendPing();
} }
@@ -98,13 +97,13 @@ public class TikTokWebSocketListener extends WebSocketClient {
@Override @Override
public void onClose(int code, String reason, boolean remote) { public void onClose(int code, String reason, boolean remote) {
tikTokEventHandler.publish(tikTokLiveClient, new TikTokDisconnectedEvent(reason)); eventHandler.publish(liveClient, new TikTokDisconnectedEvent(reason));
tikTokLiveClient.disconnect(); liveClient.disconnect();
} }
@Override @Override
public void onError(Exception error) { public void onError(Exception error) {
tikTokEventHandler.publish(tikTokLiveClient, new TikTokErrorEvent(error)); eventHandler.publish(liveClient, new TikTokErrorEvent(error));
if (isOpen()) { if (isOpen()) {
sendPing(); sendPing();
} }
@@ -132,6 +131,7 @@ public class TikTokWebSocketListener extends WebSocketClient {
@Override @Override
public void onMessage(String s) { public void onMessage(String s) {
// System.err.println(s); //TODO we are not using this method, however I wounder if there might be
//so messages that are send as String from TikTok, for example some Jsons
} }
} }

View File

@@ -22,14 +22,13 @@
*/ */
package io.github.jwdeveloper.tiktok.websocket; package io.github.jwdeveloper.tiktok.websocket;
import io.github.jwdeveloper.tiktok.TikTokLiveEventHandler;
import io.github.jwdeveloper.tiktok.data.events.TikTokConnectedEvent; 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.requests.LiveConnectionData; 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.live.LiveEventsHandler; import io.github.jwdeveloper.tiktok.live.LiveEventsHandler;
public class TikTokWebSocketOfflineClient implements SocketClient { public class TikTokWebSocketOfflineClient implements LiveSocketClient {
private final LiveEventsHandler handler; private final LiveEventsHandler handler;
private LiveClient liveClient; private LiveClient liveClient;

View File

@@ -22,18 +22,19 @@
*/ */
package io.github.jwdeveloper.tiktok.websocket; package io.github.jwdeveloper.tiktok.websocket;
import io.github.jwdeveloper.tiktok.live.LiveEventsHandler;
import org.java_websocket.WebSocket; import org.java_websocket.WebSocket;
public class TikTokWebSocketPingingTask { public class WebSocketHeartbeatTask
{
private Thread thread; private Thread thread;
private boolean isRunning = false; private boolean isRunning = false;
private final int MAX_TIMEOUT = 250; private final int MAX_TIMEOUT = 250;
private final int SLEEP_TIME = 500; private final int SLEEP_TIME = 500;
private final byte[] heartbeatBytes = {58, 2, 104, 98}; // Byte Array of "3A026862" which is TikTok's custom heartbeat value
public void run(WebSocket webSocket, long pingTaskTime) { public void run(WebSocket webSocket, long pingTaskTime) {
stop(); stop();
thread = new Thread(() -> pingTask(webSocket, pingTaskTime), "pinging-task"); thread = new Thread(() -> heartbeatTask(webSocket, pingTaskTime), "heartbeat-task");
isRunning = true; isRunning = true;
thread.start(); thread.start();
} }
@@ -44,11 +45,11 @@ public class TikTokWebSocketPingingTask {
isRunning = false; isRunning = false;
} }
private void pingTask(WebSocket webSocket, long pingTaskTime) { private void heartbeatTask(WebSocket webSocket, long pingTaskTime) {
while (isRunning) { while (isRunning) {
try { try {
if (webSocket.isOpen()) { if (webSocket.isOpen()) {
webSocket.sendPing(); webSocket.send(heartbeatBytes);
Thread.sleep(pingTaskTime + (int) (Math.random() * MAX_TIMEOUT)); Thread.sleep(pingTaskTime + (int) (Math.random() * MAX_TIMEOUT));
} else } else
Thread.sleep(SLEEP_TIME); Thread.sleep(SLEEP_TIME);

View File

@@ -0,0 +1,49 @@
package io.github.jwdeveloper.tiktok;
import io.github.jwdeveloper.dependance.implementation.DependanceContainerBuilder;
import io.github.jwdeveloper.tiktok.data.events.TikTokConnectedEvent;
import io.github.jwdeveloper.tiktok.data.events.TikTokDisconnectedEvent;
import io.github.jwdeveloper.tiktok.data.events.TikTokErrorEvent;
import io.github.jwdeveloper.tiktok.data.events.control.TikTokConnectingEvent;
import io.github.jwdeveloper.tiktok.data.events.control.TikTokPreConnectionEvent;
import io.github.jwdeveloper.tiktok.data.events.room.TikTokRoomInfoEvent;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveException;
import io.github.jwdeveloper.tiktok.live.builder.LiveClientBuilder;
import io.github.jwdeveloper.tiktok.models.ConnectionState;
import org.junit.Assert;
import org.junit.Test;
public class TikTokLiveClientTests extends TikTokTestBase {
@Override
public void onBeforeEachTest(LiveClientBuilder liveClientBuilder,
DependanceContainerBuilder containerBuilder) {
}
@Test
public void shouldThrownWhenAlreadyConnected() {
roomInfoMock().setConnectionState(ConnectionState.CONNECTED);
Assert.assertThrows(TikTokLiveException.class, () ->
{
liveClient().connect();
});
Assert.assertEquals(ConnectionState.DISCONNECTED, roomInfoMock().getConnectionState());
AssertEvents(
TikTokErrorEvent.class,
TikTokDisconnectedEvent.class
);
}
@Test
public void shouldConnect() {
liveClient().connect();
Assert.assertEquals(ConnectionState.CONNECTED, roomInfoMock().getConnectionState());
AssertEvents(
TikTokConnectingEvent.class,
TikTokPreConnectionEvent.class,
TikTokConnectedEvent.class,
TikTokRoomInfoEvent.class);
}
}

View File

@@ -0,0 +1,53 @@
package io.github.jwdeveloper.tiktok;
import io.github.jwdeveloper.dependance.implementation.DependanceContainerBuilder;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent;
import io.github.jwdeveloper.tiktok.live.LiveClient;
import io.github.jwdeveloper.tiktok.live.LiveEventsHandler;
import io.github.jwdeveloper.tiktok.live.builder.LiveClientBuilder;
import io.github.jwdeveloper.tiktok.mocks.EventsHandlerMock;
import lombok.Getter;
import lombok.experimental.Accessors;
import org.junit.Before;
/**
* Base class for the unit tests
*/
@Getter
@Accessors(fluent = true)
public abstract class TikTokTestBase {
private LiveClient liveClient;
private EventsHandlerMock eventsHandlerMock;
private TikTokRoomInfo roomInfoMock;
public void AssertEvents(Class<? extends TikTokEvent>... events) {
eventsHandlerMock.assertEvents(events);
}
@Before
public void setup() {
var builder = TikTokLive.newClient("test");
eventsHandlerMock = new EventsHandlerMock();
roomInfoMock = new TikTokRoomInfo();
roomInfoMock.setHostName("test");
liveClient = builder
.configure(liveClientSettings ->
{
liveClientSettings.setOffline(true);
liveClientSettings.setFetchGifts(false);
})
.customize(containerBuilder ->
{
containerBuilder.registerSingleton(LiveEventsHandler.class, eventsHandlerMock);
containerBuilder.registerSingleton(TikTokRoomInfo.class, roomInfoMock);
onBeforeEachTest(builder, containerBuilder);
}).build();
}
public abstract void onBeforeEachTest(LiveClientBuilder liveClientBuilder, DependanceContainerBuilder containerBuilder);
}

View File

@@ -22,7 +22,10 @@
*/ */
package io.github.jwdeveloper.tiktok.listener; package io.github.jwdeveloper.tiktok.listener;
import io.github.jwdeveloper.dependance.Dependance;
import io.github.jwdeveloper.dependance.api.DependanceContainer;
import io.github.jwdeveloper.tiktok.TikTokLiveEventHandler; import io.github.jwdeveloper.tiktok.TikTokLiveEventHandler;
import io.github.jwdeveloper.tiktok.annotations.Priority;
import io.github.jwdeveloper.tiktok.annotations.TikTokEventObserver; 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;
@@ -44,27 +47,34 @@ class TikTokListenersManagerTest {
private TikTokLiveEventHandler eventObserver; private TikTokLiveEventHandler eventObserver;
private TikTokListenersManager tikTokListenersManager; private TikTokListenersManager tikTokListenersManager;
private DependanceContainer dependanceContainer;
private LiveClient liveClient;
@BeforeEach @BeforeEach
void setUp() { void setUp() {
eventObserver = Mockito.mock(TikTokLiveEventHandler.class);
List<TikTokEventListener> listeners = new ArrayList<>(); liveClient = Mockito.mock(LiveClient.class);
tikTokListenersManager = new TikTokListenersManager(listeners, eventObserver); eventObserver = new TikTokLiveEventHandler();
dependanceContainer = Dependance.newContainer()
.registerSingleton(LiveClient.class, liveClient)
.build();
tikTokListenersManager = new TikTokListenersManager(eventObserver, dependanceContainer);
} }
@Test @Test
void addListener() { void addListener() {
TikTokEventListener listener =new TikTokEventListenerTest(); Object listener = new TikTokEventListenerTest();
tikTokListenersManager.addListener(listener); tikTokListenersManager.addListener(listener);
List<TikTokEventListener> listeners = tikTokListenersManager.getListeners(); List<Object> listeners = tikTokListenersManager.getListeners();
assertEquals(1, listeners.size()); assertEquals(1, listeners.size());
assertSame(listener, listeners.get(0)); assertSame(listener, listeners.get(0));
} }
@Test @Test
void addListener_alreadyRegistered_throwsException() { void addListener_alreadyRegistered_throwsException() {
TikTokEventListener listener = new TikTokEventListenerTest(); Object listener = new TikTokEventListenerTest();
tikTokListenersManager.addListener(listener); tikTokListenersManager.addListener(listener);
Exception exception = assertThrows(TikTokLiveException.class, () -> { Exception exception = assertThrows(TikTokLiveException.class, () -> {
@@ -76,39 +86,56 @@ class TikTokListenersManagerTest {
@Test @Test
void removeListener() { void removeListener() {
TikTokEventListener listener = new TikTokEventListenerTest(); Object listener = new TikTokEventListenerTest();
tikTokListenersManager.addListener(listener); tikTokListenersManager.addListener(listener);
tikTokListenersManager.removeListener(listener); tikTokListenersManager.removeListener(listener);
List<TikTokEventListener> listeners = tikTokListenersManager.getListeners(); List<Object> listeners = tikTokListenersManager.getListeners();
assertTrue(listeners.isEmpty()); assertTrue(listeners.isEmpty());
} }
@Test
public void shouldTriggerEvents() {
Object listener = new TikTokEventListenerTest();
tikTokListenersManager.addListener(listener);
var fakeGiftEvent = TikTokGiftEvent.of("TestRosa", 1, 1);
eventObserver.publish(liveClient, fakeGiftEvent);
}
@Test @Test
void removeListener_notRegistered_doesNotThrow() { void removeListener_notRegistered_doesNotThrow() {
TikTokEventListener listener = new TikTokEventListenerTest(); Object listener = new TikTokEventListenerTest();
assertDoesNotThrow(() -> tikTokListenersManager.removeListener(listener)); assertDoesNotThrow(() -> tikTokListenersManager.removeListener(listener));
} }
public static class TikTokEventListenerTest implements TikTokEventListener public static class TikTokEventListenerTest {
{
@TikTokEventObserver @TikTokEventObserver
public void onJoin(LiveClient client, TikTokJoinEvent joinEvent) public void onJoin(LiveClient client, TikTokJoinEvent joinEvent) {
{ System.out.println("Hello from on join" + client + " " + joinEvent);
} }
@TikTokEventObserver @TikTokEventObserver(priority = Priority.LOWEST)
public void onGift(LiveClient client, TikTokGiftEvent giftMessageEvent) public void onGift(LiveClient client, TikTokGiftEvent giftMessageEvent) {
{ System.out.println("Hello from onGift lowest priority" + client + " " + giftMessageEvent);
} }
@TikTokEventObserver @TikTokEventObserver(priority = Priority.NORMAL)
public void onEvent(LiveClient client, TikTokEvent event) public void onGift2(LiveClient client, TikTokGiftEvent giftMessageEvent) {
{ System.out.println("Hello from onGift normal priority " + client + " " + giftMessageEvent);
}
@TikTokEventObserver(priority = Priority.HIGHEST)
public void onGift3(LiveClient client, TikTokGiftEvent giftMessageEvent) {
System.out.println("Hello from onGift highest priority " + client + " " + giftMessageEvent);
}
@TikTokEventObserver(async = true)
public void onEvent(LiveClient client, TikTokEvent event) {
System.out.println("Hello from onEvent im running on the thread " + Thread.currentThread().getName());
} }
} }
} }

View File

@@ -0,0 +1,46 @@
package io.github.jwdeveloper.tiktok.mocks;
import io.github.jwdeveloper.tiktok.TikTokLiveEventHandler;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent;
import io.github.jwdeveloper.tiktok.live.LiveClient;
import java.util.ArrayList;
import java.util.List;
/**
* Cache published events,
*/
public class EventsHandlerMock extends TikTokLiveEventHandler {
private final List<TikTokEvent> publishedEvents = new ArrayList<TikTokEvent>();
@Override
public void publish(LiveClient tikTokLiveClient, TikTokEvent tikTokEvent) {
super.publish(tikTokLiveClient, tikTokEvent);
publishedEvents.add(tikTokEvent);
}
@SafeVarargs
public final void assertEvents(Class<? extends TikTokEvent>... events) {
if (events.length == 0 && !publishedEvents.isEmpty()) {
var classNames = publishedEvents.stream()
.map(e -> e.getClass().getSimpleName())
.toList();
var invokedEvents = String.join("\n", classNames);
throw new IllegalArgumentException("Not events should be invoked but there was: \n" + invokedEvents);
}
for (var i = 0; i < events.length; i++) {
var expectedEvent = events[i];
var invokedEvent = publishedEvents.get(i);
if (expectedEvent.equals(invokedEvent.getClass())) {
continue;
}
throw new RuntimeException("Expected event was " + expectedEvent + " but acctuall was " + invokedEvent.getClass());
}
}
}

View File

@@ -70,7 +70,7 @@ Maven
<dependency> <dependency>
<groupId>com.github.jwdeveloper.TikTok-Live-Java</groupId> <groupId>com.github.jwdeveloper.TikTok-Live-Java</groupId>
<artifactId>Client</artifactId> <artifactId>Client</artifactId>
<version>1.8.0-Release</version> <version>1.8.7-Release</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
</dependencies> </dependencies>
@@ -87,7 +87,7 @@ dependencyResolutionManagement {
} }
dependencies { dependencies {
implementation 'com.github.jwdeveloper.TikTok-Live-Java:Client:1.5.0-Release' implementation 'com.github.jwdeveloper.TikTok-Live-Java:Client:1.8.5-Release'
} }
``` ```
@@ -707,7 +707,7 @@ public static void main(String[] args) throws IOException {
* - second must be class that extending TikTokEvent * - second must be class that extending TikTokEvent
*/ */
public static class CustomListener implements TikTokEventListener { public static class CustomListener {
@TikTokEventObserver @TikTokEventObserver
public void onLike(LiveClient liveClient, TikTokLikeEvent event) { public void onLike(LiveClient liveClient, TikTokLikeEvent event) {

View File

@@ -41,12 +41,12 @@
<parent> <parent>
<artifactId>TikTokLiveJava</artifactId> <artifactId>TikTokLiveJava</artifactId>
<groupId>io.github.jwdeveloper.tiktok</groupId> <groupId>io.github.jwdeveloper.tiktok</groupId>
<version>1.8.0-Release</version> <version>1.8.7-Release</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<artifactId>Examples</artifactId> <artifactId>examples</artifactId>
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>io.github.jwdeveloper.tiktok</groupId> <groupId>io.github.jwdeveloper.tiktok</groupId>

View File

@@ -37,9 +37,6 @@ public class ConnectionExample {
public static void main(String[] args) throws IOException { public static void main(String[] args) throws IOException {
showLogo(); showLogo();
var gifts = TikTokLive.gifts();
TikTokLive.newClient(ConnectionExample.TIKTOK_HOSTNAME) TikTokLive.newClient(ConnectionExample.TIKTOK_HOSTNAME)
.configure(clientSettings -> .configure(clientSettings ->
{ {

View File

@@ -30,7 +30,7 @@ public class CustomMappingExample {
public static void main(String[] args) { public static void main(String[] args) {
TikTokLive.newClient("saszareznikow") TikTokLive.newClient("saszareznikow")
.onMapping(mapper -> .mappings(mapper ->
{ {
mapper.forMessage(WebcastChatMessage.class) mapper.forMessage(WebcastChatMessage.class)
.onBeforeMapping((inputBytes, messageName, mapperHelper) -> .onBeforeMapping((inputBytes, messageName, mapperHelper) ->
@@ -56,7 +56,6 @@ public class CustomMappingExample {
System.out.println("onAfter mapping, " + source.getClass().getSimpleName() + " was mapped to " + events.size() + " events"); System.out.println("onAfter mapping, " + source.getClass().getSimpleName() + " was mapped to " + events.size() + " events");
return events; return events;
}); });
/* /*
There might be cast that we don't have Webcast class for incoming message from TikTok There might be cast that we don't have Webcast class for incoming message from TikTok
`mapperHelper.bytesToProtoBufferStructure` but you can still investigate message structure `mapperHelper.bytesToProtoBufferStructure` but you can still investigate message structure

View File

@@ -29,8 +29,6 @@ public class GiftsExample {
public static void main(String[] args) { public static void main(String[] args) {
var giftsManager = TikTokLive.gifts(); var giftsManager = TikTokLive.gifts();
var giftsList = giftsManager.toList(); var giftsList = giftsManager.toList();
for (var gift : giftsList) { for (var gift : giftsList) {
System.out.println("Gift: " + gift); System.out.println("Gift: " + gift);

View File

@@ -28,21 +28,18 @@ import io.github.jwdeveloper.tiktok.data.events.TikTokErrorEvent;
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.TikTokLikeEvent; import io.github.jwdeveloper.tiktok.data.events.social.TikTokLikeEvent;
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.utils.ConsoleColors; import io.github.jwdeveloper.tiktok.utils.ConsoleColors;
import java.io.IOException; import java.io.IOException;
public class ListenerExample public class ListenerExample {
{
// <code> // <code>
/** /**
* * Listeners are an alternative way of handling events.
* Listeners are an alternative way of handling events. * I would to suggest to use then when logic of handing event
* I would to suggest to use then when logic of handing event * is more complex
* is more complex
*
*/ */
public static void main(String[] args) throws IOException { public static void main(String[] args) throws IOException {
showLogo(); showLogo();
@@ -55,24 +52,21 @@ public class ListenerExample
} }
/** /**
* * Method must meet 2 requirements to be detected
* Method in TikTokEventListener should meet 4 requirements to be detected * - must have @TikTokEventObserver annotation
* - must have @TikTokEventObserver annotation * - must have 1 parameter of type that extending TikTokEvent
* - must have 2 parameters
* - first parameter must be LiveClient
* - second must be class that extending TikTokEvent
*/ */
public static class CustomListener implements TikTokEventListener { public static class CustomListener {
@TikTokEventObserver @TikTokEventObserver
public void onLike(LiveClient liveClient, TikTokLikeEvent event) { public void onLike(TikTokLikeEvent event) {
System.out.println(event.toString()); System.out.println(event.toString());
} }
@TikTokEventObserver @TikTokEventObserver
public void onError(LiveClient liveClient, TikTokErrorEvent event) { public void onError(TikTokErrorEvent event, LiveClient liveClient) {
// event.getException().printStackTrace(); // event.getException().printStackTrace();
} }
@TikTokEventObserver @TikTokEventObserver
@@ -103,9 +97,8 @@ public class ListenerExample
} }
// </code> // </code>
private static void showLogo() private static void showLogo() {
{ System.out.println(ConsoleColors.GREEN + """
System.out.println(ConsoleColors.GREEN+"""
_____ _ _ _____ _ _ _ \s _____ _ _ _____ _ _ _ \s
|_ _(_) | _|_ _|__ | | _| | (_)_ _____\s |_ _(_) | _|_ _|__ | | _| | (_)_ _____\s

View File

@@ -32,6 +32,7 @@ public class RecorderExample {
public static void main(String[] args) { public static void main(String[] args) {
TikTokLive.newClient("bangbetmenygy") TikTokLive.newClient("bangbetmenygy")
.configure(liveClientSettings -> .configure(liveClientSettings ->
{ {

View File

@@ -6,7 +6,7 @@
<parent> <parent>
<groupId>io.github.jwdeveloper.tiktok</groupId> <groupId>io.github.jwdeveloper.tiktok</groupId>
<artifactId>TikTokLiveJava</artifactId> <artifactId>TikTokLiveJava</artifactId>
<version>1.8.0-Release</version> <version>1.8.7-Release</version>
</parent> </parent>
@@ -33,7 +33,7 @@
<dependency> <dependency>
<groupId>io.github.jwdeveloper.tiktok</groupId> <groupId>io.github.jwdeveloper.tiktok</groupId>
<artifactId>API</artifactId> <artifactId>API</artifactId>
<version>1.8.0-Release</version> <version>1.8.7-Release</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
</dependencies> </dependencies>

View File

@@ -22,9 +22,7 @@
*/ */
package io.github.jwdeveloper.tiktok.extension.collector.api; package io.github.jwdeveloper.tiktok.extension.collector.api;
import io.github.jwdeveloper.tiktok.listener.TikTokEventListener; public interface LiveDataCollector
public interface LiveDataCollector extends TikTokEventListener
{ {
} }

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.8.0-Release</version> <version>1.8.7-Release</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<artifactId>extension-recorder</artifactId> <artifactId>extension-recorder</artifactId>

View File

@@ -22,9 +22,7 @@
*/ */
package io.github.jwdeveloper.tiktok.extension.recorder.api; package io.github.jwdeveloper.tiktok.extension.recorder.api;
import io.github.jwdeveloper.tiktok.listener.TikTokEventListener; public interface LiveRecorder {
public interface LiveRecorder extends TikTokEventListener {
} }

View File

@@ -25,37 +25,41 @@ package io.github.jwdeveloper.tiktok.extension.recorder.impl;
import com.google.gson.JsonParser; import com.google.gson.JsonParser;
import io.github.jwdeveloper.tiktok.annotations.TikTokEventObserver; import io.github.jwdeveloper.tiktok.annotations.TikTokEventObserver;
import io.github.jwdeveloper.tiktok.data.events.*; import io.github.jwdeveloper.tiktok.data.events.*;
import io.github.jwdeveloper.tiktok.data.events.http.TikTokRoomDataResponseEvent; import io.github.jwdeveloper.tiktok.data.events.control.TikTokPreConnectionEvent;
import io.github.jwdeveloper.tiktok.data.settings.LiveClientSettings; import io.github.jwdeveloper.tiktok.data.settings.LiveClientSettings;
import io.github.jwdeveloper.tiktok.extension.recorder.api.LiveRecorder; import io.github.jwdeveloper.tiktok.extension.recorder.api.LiveRecorder;
import io.github.jwdeveloper.tiktok.extension.recorder.impl.data.*; import io.github.jwdeveloper.tiktok.extension.recorder.impl.data.*;
import io.github.jwdeveloper.tiktok.extension.recorder.impl.enums.LiveQuality; import io.github.jwdeveloper.tiktok.extension.recorder.impl.enums.LiveQuality;
import io.github.jwdeveloper.tiktok.extension.recorder.impl.event.TikTokLiveRecorderStartedEvent; import io.github.jwdeveloper.tiktok.extension.recorder.impl.event.*;
import io.github.jwdeveloper.tiktok.live.LiveClient; import io.github.jwdeveloper.tiktok.live.LiveClient;
import io.github.jwdeveloper.tiktok.models.ConnectionState; import io.github.jwdeveloper.tiktok.models.ConnectionState;
import javax.net.ssl.HttpsURLConnection;
import java.io.*; import java.io.*;
import java.net.URL; import java.net.URI;
import java.util.function.*; import java.net.http.*;
import java.time.Duration;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.BiConsumer;
public class RecorderListener implements LiveRecorder { public class RecorderListener implements LiveRecorder {
private final BiConsumer<RecorderSettings, LiveClient> consumer; private final BiConsumer<RecorderSettings, LiveClient> consumer;
private RecorderSettings settings; private final RecorderSettings settings;
private final AtomicBoolean token = new AtomicBoolean();
private DownloadData downloadData; private DownloadData downloadData;
private Thread liveDownloadThread; private CompletableFuture<Void> future;
public RecorderListener(BiConsumer<RecorderSettings, LiveClient> consumer) { public RecorderListener(BiConsumer<RecorderSettings, LiveClient> consumer) {
this.consumer = consumer; this.consumer = consumer;
this.settings = RecorderSettings.DEFAULT();
} }
@TikTokEventObserver @TikTokEventObserver
private void onResponse(LiveClient liveClient, TikTokRoomDataResponseEvent event) { private void onResponse(LiveClient liveClient, TikTokPreConnectionEvent event) {
settings = RecorderSettings.DEFAULT();
consumer.accept(settings, liveClient); consumer.accept(settings, liveClient);
var json = event.getLiveData().getJson(); var json = event.getUserData().getJson();
liveClient.getLogger().info("Searching for live download url"); liveClient.getLogger().info("Searching for live download url");
downloadData = settings.getPrepareDownloadData() != null ? downloadData = settings.getPrepareDownloadData() != null ?
@@ -70,96 +74,98 @@ public class RecorderListener implements LiveRecorder {
@TikTokEventObserver @TikTokEventObserver
private void onConnected(LiveClient liveClient, TikTokConnectedEvent event) { private void onConnected(LiveClient liveClient, TikTokConnectedEvent event) {
if (isConnected()) if (isConnected() || downloadData.getDownloadLiveUrl().isEmpty())
return; return;
liveDownloadThread = new Thread(() -> { var recordingStartedEvent = new TikTokLiveRecorderStartedEvent(downloadData, settings);
try {
liveClient.getLogger().info("Recording started "+liveClient.getRoomInfo().getHostName());
var url = new URL(downloadData.getFullUrl());
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
var headers = LiveClientSettings.DefaultRequestHeaders();
for (var entry : headers.entrySet()) {
connection.setRequestProperty(entry.getKey(), entry.getValue());
}
var file = settings.getOutputFile();
file.getParentFile().mkdirs();
file.createNewFile();
try (
var in = connection.getInputStream();
var fos = new FileOutputStream(file)
) {
byte[] dataBuffer = new byte[1024];
int bytesRead;
while ((!settings.isStopOnDisconnect() || liveClient.getRoomInfo().getConnectionState() == ConnectionState.CONNECTED) && (bytesRead = in.read(dataBuffer)) != -1) {
fos.write(dataBuffer, 0, bytesRead);
fos.flush();
}
} catch (IOException ignored) {
} finally {
liveClient.getLogger().severe("Stopped recording " + liveClient.getRoomInfo().getHostName());
}
} catch (Exception e) {
e.printStackTrace();
}
});
var recordingStartedEvent = new TikTokLiveRecorderStartedEvent(downloadData);
liveClient.publishEvent(recordingStartedEvent); liveClient.publishEvent(recordingStartedEvent);
if (recordingStartedEvent.isCanceled()) if (recordingStartedEvent.isCanceled())
liveClient.getLogger().info("Recording cancelled"); liveClient.getLogger().info("Recording cancelled");
else else
liveDownloadThread.start(); future = CompletableFuture.runAsync(() -> {
try {
liveClient.getLogger().info("Recording started "+liveClient.getRoomInfo().getHostName());
HttpRequest.Builder requestBuilder = HttpRequest.newBuilder(URI.create(downloadData.getFullUrl())).GET();
for (var entry : LiveClientSettings.DefaultRequestHeaders().entrySet())
requestBuilder.header(entry.getKey(), entry.getValue());
HttpResponse<InputStream> serverResponse = HttpClient.newBuilder().followRedirects(HttpClient.Redirect.NORMAL)
.connectTimeout(Duration.ofSeconds(10)).build().send(requestBuilder.build(), HttpResponse.BodyHandlers.ofInputStream());
var file = settings.getOutputFile();
file.getParentFile().mkdirs();
file.createNewFile();
try (
var in = serverResponse.body();
var fos = new FileOutputStream(file, true)
) {
byte[] dataBuffer = new byte[1024];
int bytesRead;
while (!token.get() && (!settings.isStopOnDisconnect() || liveClient.getRoomInfo().getConnectionState() == ConnectionState.CONNECTED) && (bytesRead = in.read(dataBuffer)) != -1) {
fos.write(dataBuffer, 0, bytesRead);
fos.flush();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
liveClient.getLogger().info("Stopped recording " + liveClient.getRoomInfo().getHostName());
liveClient.publishEvent(new TikTokLiveRecorderEndedEvent(settings));
}
} catch (Exception e) {
e.printStackTrace();
}
});
} }
@TikTokEventObserver @TikTokEventObserver
private void onDisconnected(LiveClient liveClient, TikTokDisconnectedEvent event) { private void onDisconnected(LiveClient liveClient, TikTokDisconnectedEvent event) {
if (isConnected() && settings.isStopOnDisconnect()) if (isConnected() && settings.isStopOnDisconnect())
liveDownloadThread.interrupt(); token.set(true);
} }
@TikTokEventObserver @TikTokEventObserver
private void onLiveEnded(LiveClient liveClient, TikTokLiveEndedEvent event) { private void onLiveEnded(LiveClient liveClient, TikTokLiveEndedEvent event) {
if (isConnected()) if (isConnected())
liveDownloadThread.interrupt(); token.set(true);
} }
private DownloadData mapToDownloadData(String json) { private DownloadData mapToDownloadData(String json) {
try {
var parsedJson = JsonParser.parseString(json); var parsedJson = JsonParser.parseString(json);
var jsonObject = parsedJson.getAsJsonObject(); var jsonObject = parsedJson.getAsJsonObject();
var streamDataJson = jsonObject.getAsJsonObject("data") var streamDataJson = jsonObject.getAsJsonObject("data")
.getAsJsonObject("stream_url") .getAsJsonObject("liveRoom")
.getAsJsonObject("live_core_sdk_data") .getAsJsonObject("streamData")
.getAsJsonObject("pull_data") .getAsJsonObject("pull_data")
.get("stream_data") .get("stream_data")
.getAsString(); .getAsString();
var streamDataJsonObject = JsonParser.parseString(streamDataJson).getAsJsonObject(); var streamDataJsonObject = JsonParser.parseString(streamDataJson).getAsJsonObject();
var urlLink = streamDataJsonObject.getAsJsonObject("data") var urlLink = streamDataJsonObject.getAsJsonObject("data")
.getAsJsonObject(LiveQuality.origin.name()) .getAsJsonObject(LiveQuality.origin.name())
.getAsJsonObject("main") .getAsJsonObject("main")
.get("flv") .get("flv")
.getAsString(); .getAsString();
var sessionId = streamDataJsonObject.getAsJsonObject("common") var sessionId = streamDataJsonObject.getAsJsonObject("common")
.get("session_id") .get("session_id")
.getAsString(); .getAsString();
//main //main
//https://pull-f5-tt03.fcdn.eu.tiktokcdn.com/stage/stream-3284937501738533765.flv?session_id=136-20240109000954BF818F1B3A8E5E39E238&_webnoredir=1 //https://pull-f5-tt03.fcdn.eu.tiktokcdn.com/stage/stream-3284937501738533765.flv?session_id=136-20240109000954BF818F1B3A8E5E39E238&_webnoredir=1
//Working //Working
//https://pull-f5-tt03.fcdn.eu.tiktokcdn.com/game/stream-3284937501738533765_sd5.flv?_session_id=136-20240109001052D91FDBC00143211020C8.1704759052997&_webnoredir=1 //https://pull-f5-tt03.fcdn.eu.tiktokcdn.com/game/stream-3284937501738533765_sd5.flv?_session_id=136-20240109001052D91FDBC00143211020C8.1704759052997&_webnoredir=1
//https://pull-f5-tt02.fcdn.eu.tiktokcdn.com/stage/stream-3861399216374940610_uhd5.flv?_session_id=136-20240109000223D0BAA1A83974490EE630.1704758544391&_webnoredir=1 //https://pull-f5-tt02.fcdn.eu.tiktokcdn.com/stage/stream-3861399216374940610_uhd5.flv?_session_id=136-20240109000223D0BAA1A83974490EE630.1704758544391&_webnoredir=1
return new DownloadData(urlLink, sessionId); return new DownloadData(urlLink, sessionId);
} catch (Exception e) {
return new DownloadData("", "");
}
} }
private boolean isConnected() { private boolean isConnected() {
return liveDownloadThread != null && liveDownloadThread.isAlive(); return future != null && !future.isDone();
} }
} }

View File

@@ -34,6 +34,6 @@ public class DownloadData {
private String sessionId; private String sessionId;
public String getFullUrl() { public String getFullUrl() {
return downloadLiveUrl + "&_webnoredir=1&session_id=" + sessionId; return downloadLiveUrl + (downloadLiveUrl.contains("?") ? "&" : "?") + "_webnoredir=1&session_id=" + sessionId;
} }
} }

View File

@@ -20,19 +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.data.events.http; package io.github.jwdeveloper.tiktok.extension.recorder.impl.event;
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.requests.LiveData; import io.github.jwdeveloper.tiktok.extension.recorder.impl.data.*;
import lombok.AllArgsConstructor; import lombok.*;
import lombok.Getter;
@Getter @Getter
@AllArgsConstructor public class TikTokLiveRecorderEndedEvent extends TikTokEvent {
@EventMeta(eventType = EventType.Debug)
public class TikTokRoomDataResponseEvent extends TikTokEvent private final RecorderSettings settings;
{
private final LiveData.Response liveData; public TikTokLiveRecorderEndedEvent(RecorderSettings settings) {
this.settings = settings;
}
} }

View File

@@ -23,21 +23,21 @@
package io.github.jwdeveloper.tiktok.extension.recorder.impl.event; package io.github.jwdeveloper.tiktok.extension.recorder.impl.event;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent; import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent;
import io.github.jwdeveloper.tiktok.extension.recorder.impl.data.DownloadData; import io.github.jwdeveloper.tiktok.extension.recorder.impl.data.*;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
@AllArgsConstructor @AllArgsConstructor
@Getter @Getter
public class TikTokLiveRecorderStartedEvent extends TikTokEvent { public class TikTokLiveRecorderStartedEvent extends TikTokEvent {
DownloadData downloadData; private final DownloadData downloadData;
private final RecorderSettings settings;
@Setter @Setter boolean canceled;
boolean canceled;
public TikTokLiveRecorderStartedEvent(DownloadData downloadData) { public TikTokLiveRecorderStartedEvent(DownloadData downloadData, RecorderSettings settings) {
this.downloadData = downloadData; this.downloadData = downloadData;
this.settings = settings;
} }
} }

7
future.md Normal file
View File

@@ -0,0 +1,7 @@
Future plans:
[ ] Publish project to Maven repository instead of JITPACK
[ ] Dynamically generates readme.md when new version is released
[ ] Covers all possible classes from Client module with unit test
[ ] Add tests check to GitHub publish action
[ ] Generates documentation based on API module and publish it with GitHub pages

View File

@@ -7,11 +7,11 @@
<groupId>io.github.jwdeveloper.tiktok</groupId> <groupId>io.github.jwdeveloper.tiktok</groupId>
<artifactId>TikTokLiveJava</artifactId> <artifactId>TikTokLiveJava</artifactId>
<packaging>pom</packaging> <packaging>pom</packaging>
<version>1.8.0-Release</version> <version>1.8.7-Release</version>
<modules> <modules>
<module>API</module> <module>API</module>
<module>Client</module> <module>Client</module>
<module>Examples</module> <module>examples</module>
<module>tools-readme</module> <module>tools-readme</module>
<module>extension-recorder</module> <module>extension-recorder</module>

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.8.0-Release</version> <version>1.8.7-Release</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
@@ -34,12 +34,6 @@
<version>2.13.0</version> <version>2.13.0</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<dependency>
<groupId>com.github.jwdeveloper.Descrabble</groupId>
<artifactId>Descrabble-Full</artifactId>
<version>0.0.11-Release</version>
<scope>compile</scope>
</dependency>
<dependency> <dependency>
<groupId>org.reflections</groupId> <groupId>org.reflections</groupId>
<artifactId>reflections</artifactId> <artifactId>reflections</artifactId>

View File

@@ -1,17 +0,0 @@
package io.github.jwdeveloper.tiktok;
import io.github.jwdeveloper.descrabble.api.DescriptionDecorator;
import io.github.jwdeveloper.descrabble.api.elements.Element;
import io.github.jwdeveloper.descrabble.api.elements.ElementFactory;
public class EventsDecorator implements DescriptionDecorator {
@Override
public void decorate(Element root, ElementFactory factory)
{
}
}

View File

@@ -41,7 +41,6 @@ public class EventsInfoGenerator {
System.out.println(res); System.out.println(res);
} }
public String run() { public String run() {
var events = getEventsDtos(); var events = getEventsDtos();
var builder = new StringBuilder(); var builder = new StringBuilder();

View File

@@ -1,8 +1,6 @@
package io.github.jwdeveloper.tiktok; package io.github.jwdeveloper.tiktok;
import io.github.jwdeveloper.descrabble.api.DescriptionGenerator;
import io.github.jwdeveloper.descrabble.framework.Descrabble;
import io.github.jwdeveloper.descrabble.plugin.github.DescrabbleGithub;
import org.apache.commons.io.FileUtils; import org.apache.commons.io.FileUtils;
import java.io.File; import java.io.File;
@@ -23,16 +21,6 @@ public class Main
var output = System.getProperty("user.dir"); var output = System.getProperty("user.dir");
DescriptionGenerator generator = Descrabble.create()
.withTemplate(targetFile)
.withVariable("version", version)
.withDecorator(new EventsDecorator())
.withPlugin(DescrabbleGithub.plugin("README.md"))
.build();
generator.generate(output);
targetFile.delete();
inputStream.close();
} }
} }

View File

@@ -27,7 +27,6 @@ public class ReadmeGenerator {
public static void main(String[] args) { public static void main(String[] args) {
var generator = new ReadmeGenerator(); var generator = new ReadmeGenerator();
generator.generate(); generator.generate();
} }
public void generate() { public void generate() {

View File

@@ -88,8 +88,6 @@ Do you prefer other programming languages?
{{if item is 2}} {{if item is 2}}
my name is {{item.name}}
{{else}} {{else}}
{{end}} {{end}}
@@ -98,7 +96,6 @@ my name is {{item.name}}
<br> <br>
## Listeners ## Listeners
```java ```java
{{listener-content}} {{listener-content}}
``` ```