Compare commits

...

51 Commits

Author SHA1 Message Date
JW
818c4cb364 Update maven-publish.yml 2023-09-04 12:06:33 +02:00
JW
911e2b12a5 Merge remote-tracking branch 'origin/master' 2023-09-04 12:05:19 +02:00
JW
1aff710523 Changes:
`onWebsocketMessage()` TikTokWebsocketMessageEvent new event that is
   triggered when new ProtocolBuffer message come from TikTok server.
   Should be mainly use for debuging purpose

Bugs:
 - Fixed bug: WebcastSocialMessage was always triggering `TikTokShareEvent` events such as `TikTokLikeEvent`, `TikTokFollowEvent`, `TikTokShareEvent`, `TikTokJoinEvent` was ignored

 - Fixed bug: Websocket was disconnecting when there was no incoming events for the while. Fixed by implementing background loop that pinging TikTok server every few ms.

 - Fixed bug: Disconnect method was not working
2023-09-04 12:05:13 +02:00
JW
c0f8e6d904 Update README.md 2023-09-04 11:41:33 +02:00
JW
b059afd621 Update README.md 2023-08-31 00:22:45 +02:00
JW
1275878822 Merge pull request #3 from isaackogan/patch-1
Update README.md
2023-08-30 22:39:44 +02:00
Isaac Kogan
761f3ab633 Update README.md 2023-08-30 15:58:50 -04:00
Jacek Wolniewicz
66def92316 quick fix 2023-08-30 21:50:25 +02:00
Jacek Wolniewicz
cc85d7c124 Changes:
- New event `onSuccessResponseMapping`
  triggered when Webcast.Message was  successfully mapped to TikTokEvent

- New Project Tools-collector:
  Tool generates instance of SqlLite
  database and collect to it both to
 events and exceptions occurred while TikTokLive client was running
2023-08-30 21:47:57 +02:00
Jacek Wolniewicz
384cfade5a Changes:
- New event `onSuccessResponseMapping`
  triggered when Webcast.Message was  successfully mapped to TikTokEvent

- New Project Tools-collector:
  Tool generates instance of SqlLite
  database and collect to it both to
 events and exceptions occurred while TikTokLive client was running
2023-08-30 21:47:45 +02:00
JW
483dceadcf Update README.md 2023-08-27 14:23:25 +02:00
JW
5f8cba5126 Update README.md 2023-08-26 03:42:09 +02:00
JW
f9966c9a5f Update README.md 2023-08-26 01:34:43 +02:00
JW
48a79736ad Update README.md 2023-08-26 01:34:19 +02:00
JW
eb82d0df78 Update README.md 2023-08-25 21:03:51 +02:00
GitHub Action
4ed821925d Update version in pom.xml 2023-08-25 19:03:24 +00:00
JW
6e9244aa67 Changes:
- Fixed bug: WebcastSocialMessage was always triggering `TikTokShareEvent` events such as `TikTokLikeEvent`, `TikTokFollowEvent`, `TikTokShareEvent`, `TikTokJoinEvent` was ignored
2023-08-25 21:01:56 +02:00
JW
fadb1ab267 changeing client name 2023-08-24 22:37:56 +02:00
JW
4273375eb9 Update README.md 2023-08-24 16:51:04 +02:00
JW
3daeee6316 Update README.md 2023-08-24 16:45:25 +02:00
GitHub Action
cde38df1b3 Update version in pom.xml 2023-08-24 14:38:53 +00:00
JW
15d6351d65 Changes:
- Fixed bug: library was not working on certain java versions
   due to different WebSocket implementation. Instead of using java websocket api now there is `org.java-websocket`
2023-08-24 16:36:40 +02:00
JW
44ba999b83 Update README.md 2023-08-23 21:56:08 +02:00
JW
5cf0d30962 Update README.md 2023-08-23 21:10:12 +02:00
JW
71ebc6e05e Update README.md 2023-08-23 21:00:37 +02:00
GitHub Action
c6d09927a0 Update version in pom.xml 2023-08-23 18:57:15 +00:00
JW
f03cb14262 Merge remote-tracking branch 'origin/master' 2023-08-23 20:55:46 +02:00
JW
d9ef60ccad Changes:
- Implementation on all features in `clientSettings`
  - Code optimization
  - More detail exceptions
  - Downloading gifts
2023-08-23 20:55:40 +02:00
JW
6fb89e72d4 Update README.md 2023-08-22 21:10:34 +02:00
JW
230764ed6a Update README.md 2023-08-22 21:08:38 +02:00
JW
b9a4fb193e Update README.md 2023-08-22 21:03:55 +02:00
JW
af209fc2ca Update README.md 2023-08-22 21:00:16 +02:00
JW
74bfe0b9e7 Merge remote-tracking branch 'origin/master' 2023-08-22 20:58:50 +02:00
JW
3545167873 Update example project 2023-08-22 20:58:45 +02:00
JW
c22483043d Update README.md 2023-08-22 20:51:17 +02:00
GitHub Action
8155d49366 Update version in pom.xml 2023-08-22 18:49:05 +00:00
JW
321b7c0eda Merge remote-tracking branch 'origin/master' 2023-08-22 20:47:33 +02:00
JW
26c7db8f99 Fix messages
- WebcastSocialMessage
  - ImDeleteMessage
2023-08-22 20:47:27 +02:00
JW
5f7ead2f05 Update README.md 2023-08-22 20:01:05 +02:00
GitHub Action
dffae3a521 Update version in pom.xml 2023-08-22 18:00:21 +00:00
JW
6254443755 Merge remote-tracking branch 'origin/master' 2023-08-22 19:58:51 +02:00
JW
c001eacbce Remove old test 2023-08-22 19:58:46 +02:00
JW
1bbb704d14 Update README.md 2023-08-22 19:54:56 +02:00
JW
8c3a5c6627 Remove old test 2023-08-22 19:54:10 +02:00
JW
470b154c5e Merge remote-tracking branch 'origin/master' 2023-08-22 19:53:39 +02:00
JW
2391b12598 Fix Message parsing for
- LikeMessage
- MessageWebcastGiftMessage
- MessageWebcastChatMessage
2023-08-22 19:53:33 +02:00
JW
cb68050e24 Update README.md 2023-08-22 19:14:46 +02:00
JW
a9e347b8da Update README.md 2023-08-22 17:50:22 +02:00
JW
73823c82ea Update README.md 2023-08-22 17:49:35 +02:00
JW
e0542d39af Update README.md 2023-08-22 17:46:42 +02:00
GitHub Action
d33dab0a98 Update version in pom.xml 2023-08-22 15:40:34 +00:00
86 changed files with 1729 additions and 868 deletions

View File

@@ -1,4 +1,4 @@
name: Publish Artifacts
name: Publish New Version
on:
workflow_dispatch:

79
.gitignore vendored
View File

@@ -1,3 +1,82 @@
# Project exclude paths
/API/target/
/Client/target/
*.db
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# AWS User-specific
.idea/**/aws.xml
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/artifacts
# .idea/compiler.xml
# .idea/jarRepositories.xml
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# *.iml
# *.ipr
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# SonarLint plugin
.idea/sonarlint/
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser

View File

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

View File

@@ -3,6 +3,8 @@ package io.github.jwdeveloper.tiktok;
import lombok.Data;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
@Data
@@ -10,62 +12,58 @@ public class ClientSettings {
/// <summary>
/// Timeout for Connections
/// </summary>
private Duration Timeout;
/// <summary>
/// Polling-Interval for Socket-Connection
/// </summary
private Duration PollingInterval;
/// <summary>
/// Proxy for Connection
/// </summary>
private Duration timeout;
// public RotatingProxy Proxy;
/// <summary>
/// ISO-Language for Client
/// </summary>
private String ClientLanguage;
/// <summary>
/// Size for Buffer for Socket-Connection
/// </summary>
private int SocketBufferSize;
private String clientLanguage;
/// <summary>
/// Whether to Retry if Connection Fails
/// </summary>
private boolean RetryOnConnectionFailure;
private boolean retryOnConnectionFailure;
/// <summary>
/// Wait to connect again for selected amount of time
/// </summary>
private Duration retryConnectionTimeout;
/// <summary>
/// Whether to handle Messages received from Room when Connecting
/// </summary>
private boolean HandleExistingMessagesOnConnect;
private boolean handleExistingMessagesOnConnect;
/// <summary>
/// Whether to download List of Gifts for Room when Connecting
/// </summary>
private boolean DownloadGiftInfo;
private boolean downloadGiftInfo;
/// <summary>
/// Whether to print Logs to Console
/// </summary>
private boolean PrintToConsole;
private boolean printToConsole;
/// <summary>
/// LoggingLevel for Logs
/// </summary>
private Level LogLevel;
private Level logLevel;
/// <summary>
/// Whether to print Base64-Data for Messages to Console
/// </summary>
private boolean PrintMessageData;
private boolean printMessageData;
/// <summary>
/// Whether to check Messages for Unparsed Data
/// Tiktok user name
/// </summary>
private boolean CheckForUnparsedData;
private String hostName;
/// <summary>
/// Parameters used in requests to tiktok api
/// </summary>
private Map<String, Object> clientParameters;
}

View File

@@ -39,18 +39,16 @@ public class Constants {
public static ClientSettings DefaultClientSettings() {
var clientSettings = new ClientSettings();
clientSettings.setTimeout(Duration.ofSeconds(DEFAULT_TIMEOUT));
clientSettings.setPollingInterval(Duration.ofSeconds(DEFAULT_POLLTIME));
clientSettings.setClientLanguage("en-US");
clientSettings.setHandleExistingMessagesOnConnect(true);
clientSettings.setDownloadGiftInfo(true);
clientSettings.setRetryOnConnectionFailure(true);
clientSettings.setSocketBufferSize(500_000);
clientSettings.setPrintToConsole(true);
clientSettings.setRetryOnConnectionFailure(false);
clientSettings.setRetryConnectionTimeout(Duration.ofSeconds(1));
clientSettings.setPrintToConsole(false);
clientSettings.setLogLevel(Level.ALL);
clientSettings.setCheckForUnparsedData(false);
clientSettings.setPrintMessageData(false);
clientSettings.setClientParameters(Constants.DefaultClientParams());
return clientSettings;
}

View File

@@ -2,94 +2,94 @@ package io.github.jwdeveloper.tiktok.events;
import io.github.jwdeveloper.tiktok.events.messages.*;
import java.util.function.Consumer;
public interface TikTokEventBuilder<T> {
T onUnhandledSocial(Consumer<TikTokUnhandledSocialEvent> event);
T onUnhandledSocial(TikTokEventConsumer<TikTokUnhandledSocialEvent> event);
T onLinkMicFanTicket(Consumer<TikTokLinkMicFanTicketEvent> event);
T onLinkMicFanTicket(TikTokEventConsumer<TikTokLinkMicFanTicketEvent> event);
T onEnvelope(Consumer<TikTokEnvelopeEvent> event);
T onEnvelope(TikTokEventConsumer<TikTokEnvelopeEvent> event);
T onShopMessage(Consumer<TikTokShopMessageEvent> event);
T onShopMessage(TikTokEventConsumer<TikTokShopMessageEvent> event);
T onDetectMessage(Consumer<TikTokDetectMessageEvent> event);
T onDetectMessage(TikTokEventConsumer<TikTokDetectMessageEvent> event);
T onLinkLayerMessage(Consumer<TikTokLinkLayerMessageEvent> event);
T onLinkLayerMessage(TikTokEventConsumer<TikTokLinkLayerMessageEvent> event);
T onConnected(Consumer<TikTokConnectedEvent> event);
T onConnected(TikTokEventConsumer<TikTokConnectedEvent> event);
T onCaption(Consumer<TikTokCaptionEvent> event);
T onCaption(TikTokEventConsumer<TikTokCaptionEvent> event);
T onQuestion(Consumer<TikTokQuestionEvent> event);
T onQuestion(TikTokEventConsumer<TikTokQuestionEvent> event);
T onRoomPinMessage(Consumer<TikTokRoomPinMessageEvent> event);
T onRoomPinMessage(TikTokEventConsumer<TikTokRoomPinMessageEvent> event);
T onRoomMessage(Consumer<TikTokRoomMessageEvent> event);
T onRoomMessage(TikTokEventConsumer<TikTokRoomMessageEvent> event);
T onLivePaused(Consumer<TikTokLivePausedEvent> event);
T onLivePaused(TikTokEventConsumer<TikTokLivePausedEvent> event);
T onLike(Consumer<TikTokLikeEvent> event);
T onLike(TikTokEventConsumer<TikTokLikeEvent> event);
T onLinkMessage(Consumer<TikTokLinkMessageEvent> event);
T onLinkMessage(TikTokEventConsumer<TikTokLinkMessageEvent> event);
T onBarrageMessage(Consumer<TikTokBarrageMessageEvent> event);
T onBarrageMessage(TikTokEventConsumer<TikTokBarrageMessageEvent> event);
T onGiftMessage(Consumer<TikTokGiftMessageEvent> event);
T onGiftMessage(TikTokEventConsumer<TikTokGiftMessageEvent> event);
T onLinkMicArmies(Consumer<TikTokLinkMicArmiesEvent> event);
T onLinkMicArmies(TikTokEventConsumer<TikTokLinkMicArmiesEvent> event);
T onEmote(Consumer<TikTokEmoteEvent> event);
T onEmote(TikTokEventConsumer<TikTokEmoteEvent> event);
T onUnauthorizedMember(Consumer<TikTokUnauthorizedMemberEvent> event);
T onUnauthorizedMember(TikTokEventConsumer<TikTokUnauthorizedMemberEvent> event);
T onInRoomBanner(Consumer<TikTokInRoomBannerEvent> event);
T onInRoomBanner(TikTokEventConsumer<TikTokInRoomBannerEvent> event);
T onLinkMicMethod(Consumer<TikTokLinkMicMethodEvent> event);
T onLinkMicMethod(TikTokEventConsumer<TikTokLinkMicMethodEvent> event);
T onSubscribe(Consumer<TikTokSubscribeEvent> event);
T onSubscribe(TikTokEventConsumer<TikTokSubscribeEvent> event);
T onPollMessage(Consumer<TikTokPollMessageEvent> event);
T onPollMessage(TikTokEventConsumer<TikTokPollMessageEvent> event);
T onFollow(Consumer<TikTokFollowEvent> event);
T onFollow(TikTokEventConsumer<TikTokFollowEvent> event);
T onRoomViewerData(Consumer<TikTokRoomViewerDataEvent> event);
T onRoomViewerData(TikTokEventConsumer<TikTokRoomViewerDataEvent> event);
T onGoalUpdate(Consumer<TikTokGoalUpdateEvent> event);
T onGoalUpdate(TikTokEventConsumer<TikTokGoalUpdateEvent> event);
T onComment(Consumer<TikTokCommentEvent> event);
T onComment(TikTokEventConsumer<TikTokCommentEvent> event);
T onRankUpdate(Consumer<TikTokRankUpdateEvent> event);
T onRankUpdate(TikTokEventConsumer<TikTokRankUpdateEvent> event);
T onIMDelete(Consumer<TikTokIMDeleteEvent> event);
T onIMDelete(TikTokEventConsumer<TikTokIMDeleteEvent> event);
T onLiveEnded(Consumer<TikTokLiveEndedEvent> event);
T onLiveEnded(TikTokEventConsumer<TikTokLiveEndedEvent> event);
T onError(Consumer<TikTokErrorEvent> event);
T onError(TikTokEventConsumer<TikTokErrorEvent> event);
T onUnhandled(Consumer<TikTokUnhandledEvent> event);
T onUnhandled(TikTokEventConsumer<TikTokUnhandledEvent> event);
T onJoin(Consumer<TikTokJoinEvent> event);
T onJoin(TikTokEventConsumer<TikTokJoinEvent> event);
T onRankText(Consumer<TikTokRankTextEvent> event);
T onRankText(TikTokEventConsumer<TikTokRankTextEvent> event);
T onShare(Consumer<TikTokShareEvent> event);
T onShare(TikTokEventConsumer<TikTokShareEvent> event);
T onUnhandledMember(Consumer<TikTokUnhandledMemberEvent> event);
T onUnhandledMember(TikTokEventConsumer<TikTokUnhandledMemberEvent> event);
T onSubNotify(Consumer<TikTokSubNotifyEvent> event);
T onSubNotify(TikTokEventConsumer<TikTokSubNotifyEvent> event);
T onLinkMicBattle(Consumer<TikTokLinkMicBattleEvent> event);
T onLinkMicBattle(TikTokEventConsumer<TikTokLinkMicBattleEvent> event);
T onDisconnected(Consumer<TikTokDisconnectedEvent> event);
T onDisconnected(TikTokEventConsumer<TikTokDisconnectedEvent> event);
T onGiftBroadcast(Consumer<TikTokGiftBroadcastEvent> event);
T onGiftBroadcast(TikTokEventConsumer<TikTokGiftBroadcastEvent> event);
T onUnhandledControl(Consumer<TikTokUnhandledControlEvent> event);
T onUnhandledControl(TikTokEventConsumer<TikTokUnhandledControlEvent> event);
T onEvent(TikTokEventConsumer<TikTokEvent> event);
T onWebsocketMessage(TikTokEventConsumer<TikTokWebsocketMessageEvent> event);
T onEvent(Consumer<TikTokEvent> event);
}

View File

@@ -2,7 +2,7 @@ package io.github.jwdeveloper.tiktok.events;
import io.github.jwdeveloper.tiktok.live.LiveClient;
public interface TikTokLiveEvent<T extends TikTokEvent>
public interface TikTokEventConsumer<T extends TikTokEvent>
{
void onEvent(LiveClient liveClient, T event);
}

View File

@@ -3,12 +3,14 @@ package io.github.jwdeveloper.tiktok.events.messages;
import io.github.jwdeveloper.tiktok.annotations.Nullable;
import io.github.jwdeveloper.tiktok.events.TikTokEvent;
import io.github.jwdeveloper.tiktok.events.objects.Gift;
import io.github.jwdeveloper.tiktok.events.objects.TikTokGift;
import io.github.jwdeveloper.tiktok.events.objects.User;
import io.github.jwdeveloper.tiktok.messages.WebcastGiftMessage;
import lombok.Getter;
@Getter
public class TikTokGiftMessageEvent extends TikTokEvent {
private final Gift gift;
@Nullable
@@ -25,10 +27,9 @@ public class TikTokGiftMessageEvent extends TikTokEvent {
private final Integer streakIndex;
public TikTokGiftMessageEvent(WebcastGiftMessage msg) {
super(msg.getHeader());;
super(msg.getHeader());
gift = new Gift(msg.getGiftDetails());
if(msg.hasSender())
{
if (msg.hasSender()) {
sender = new User(msg.getSender());
}
purchaseId = msg.getLogId();

View File

@@ -6,12 +6,10 @@ import lombok.Getter;
@Getter
public class TikTokIMDeleteEvent extends TikTokEvent {
private final String data1;
private final String data2;
private final byte[] data;
public TikTokIMDeleteEvent(WebcastImDeleteMessage msg) {
super(msg.getHeader());;
data1 = msg.getData1();
data2 = msg.getData2();
super(msg.getHeader());
data = msg.getData().toByteArray();
}
}

View File

@@ -20,10 +20,15 @@ public class TikTokRankUpdateEvent extends TikTokEvent {
var rankData = msg.getData().getRankings();
eventType = rankData.getType();
label = rankData.getLabel();
if(rankData.getDetailsList().isEmpty())
{
rank = "";
}
else
{
rank = rankData.getDetails(0).getLabel();
}
color = rankData.getColor().getColor();
}
public TikTokRankUpdateEvent(WebcastRankUpdateMessage msg) {
@@ -31,7 +36,14 @@ public class TikTokRankUpdateEvent extends TikTokEvent {
var rankData = msg.getData().getRankData();
eventType = rankData.getType();
label = rankData.getLabel();
if(rankData.getDetailsList().isEmpty())
{
rank = "";
}
else
{
rank = rankData.getDetails(0).getLabel();
}
color = rankData.getColor().getColor();
}

View File

@@ -0,0 +1,19 @@
package io.github.jwdeveloper.tiktok.events.messages;
import io.github.jwdeveloper.tiktok.events.TikTokEvent;
import io.github.jwdeveloper.tiktok.messages.WebcastResponse;
import lombok.AllArgsConstructor;
import lombok.Data;
/**
* Happens when TikTok websocket receive message from server
*/
@Data
@AllArgsConstructor
public class TikTokWebsocketMessageEvent extends TikTokEvent
{
private TikTokEvent event;
private WebcastResponse.Message message;
}

View File

@@ -7,14 +7,14 @@ import java.util.List;
@Getter
public class Badge {
private final ComboBadge comboBadges;
private ComboBadge comboBadges;
private final List<TextBadge> textBadges;
private final List<ImageBadge> imageBadges;
public Badge(io.github.jwdeveloper.tiktok.messages.Badge badge) {
textBadges = badge.getTextBadgesList().stream().map(b -> new TextBadge(b.getType(), b.getName())).toList();
imageBadges = badge.getImageBadgesList().stream().map(b -> new ImageBadge(b.getDisplayType(), new Picture(b.getImage()))).toList();
comboBadges = new ComboBadge(new Picture(badge.getComplexBadge().getImageUrl()), badge.getComplexBadge().getData());
comboBadges = new ComboBadge(new Picture("badge.getComplexBadge().getImageUrl()"), badge.getComplexBadge().getData());
}

View File

@@ -0,0 +1,38 @@
package io.github.jwdeveloper.tiktok.exceptions;
import io.github.jwdeveloper.tiktok.messages.WebcastResponse;
import lombok.Getter;
import java.util.Base64;
public class TikTokLiveMessageException extends TikTokLiveException {
@Getter
private final WebcastResponse.Message webcastMessage;
@Getter
private final WebcastResponse webcastResponse;
public TikTokLiveMessageException(WebcastResponse.Message message,
WebcastResponse webcastResponse,
Throwable cause) {
super("Error while handling Message: " + message.getType() + ": \n", cause);
this.webcastMessage = message;
this.webcastResponse = webcastResponse;
}
public String messageName()
{
return webcastMessage.getType();
}
public String messageToBase64()
{
return Base64.getEncoder().encodeToString(webcastMessage.getBinary().toByteArray());
}
public String webcastResponseToBase64()
{
return Base64.getEncoder().encodeToString(webcastResponse.toByteArray());
}
}

View File

@@ -1,23 +0,0 @@
package io.github.jwdeveloper.tiktok.exceptions;
public class TikTokLiveMessageParsingException extends TikTokLiveException
{
public TikTokLiveMessageParsingException() {
}
public TikTokLiveMessageParsingException(String message) {
super(message);
}
public TikTokLiveMessageParsingException(String message, Throwable cause) {
super(message, cause);
}
public TikTokLiveMessageParsingException(Throwable cause) {
super(cause);
}
public TikTokLiveMessageParsingException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}

View File

@@ -0,0 +1,8 @@
package io.github.jwdeveloper.tiktok.exceptions;
public class TikTokLiveOfflineHostException extends TikTokLiveException
{
public TikTokLiveOfflineHostException(String message) {
super(message);
}
}

View File

@@ -0,0 +1,23 @@
package io.github.jwdeveloper.tiktok.exceptions;
public class TikTokLiveRequestException extends TikTokLiveException
{
public TikTokLiveRequestException() {
}
public TikTokLiveRequestException(String message) {
super(message);
}
public TikTokLiveRequestException(String message, Throwable cause) {
super(message, cause);
}
public TikTokLiveRequestException(Throwable cause) {
super(cause);
}
public TikTokLiveRequestException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}

View File

@@ -0,0 +1,15 @@
package io.github.jwdeveloper.tiktok.exceptions;
public class TikTokMessageMappingException extends TikTokLiveException
{
public TikTokMessageMappingException(Class<?> inputClazz, Class<?> outputClass, Throwable throwable)
{
super("Unable to handle mapping from class: " + inputClazz.getSimpleName() + " to class " + outputClass.getSimpleName(),throwable);
}
public TikTokMessageMappingException(Class<?> inputClazz, Class<?> outputClass, String message)
{
super("Unable to handle mapping from class: " + inputClazz.getSimpleName() + " to class " + outputClass.getSimpleName()+": "+message);
}
}

View File

@@ -0,0 +1,15 @@
package io.github.jwdeveloper.tiktok.exceptions;
import lombok.Getter;
public class TikTokProtocolBufferException extends TikTokLiveException
{
@Getter
private final byte[] bytes;
public TikTokProtocolBufferException(String message, byte[] bytes, Throwable cause)
{
super(message, cause);
this.bytes = bytes;
}
}

View File

@@ -0,0 +1,10 @@
package io.github.jwdeveloper.tiktok.handler;
import io.github.jwdeveloper.tiktok.events.TikTokEvent;
import io.github.jwdeveloper.tiktok.messages.WebcastResponse;
public interface TikTokMessageHandler
{
TikTokEvent handle(WebcastResponse.Message message) throws Exception;
}

View File

@@ -1,7 +0,0 @@
package io.github.jwdeveloper.tiktok.http.Resource;
import lombok.Data;
@Data
public class ClientFetchDataResponse {
}

View File

@@ -0,0 +1,14 @@
package io.github.jwdeveloper.tiktok.live;
import io.github.jwdeveloper.tiktok.events.objects.TikTokGift;
import io.github.jwdeveloper.tiktok.models.GiftId;
import io.github.jwdeveloper.tiktok.models.gifts.TikTokGiftInfo;
import java.util.Map;
public interface GiftManager
{
Map<Integer, TikTokGiftInfo> getGiftsInfo();
Map<GiftId, TikTokGift> getActiveGifts();
}

View File

@@ -6,5 +6,6 @@ public interface LiveClient {
void disconnect();
GiftManager getGiftManager();
LiveRoomInfo getRoomInfo();
}

View File

@@ -3,7 +3,6 @@ package io.github.jwdeveloper.tiktok.live;
public interface LiveRoomInfo
{
int getViewersCount();
String getRoomId();
String getUserName();
}

View File

@@ -7,7 +7,7 @@ import lombok.Data;
@AllArgsConstructor
public class GiftId
{
public long Gift;
private long giftId;
public String UserName;
private String userName;
}

View File

@@ -0,0 +1,6 @@
package io.github.jwdeveloper.tiktok.models;
public enum GiftStrike
{
BEGIN, UPDATE, ENDED
}

View File

@@ -3,8 +3,7 @@ package io.github.jwdeveloper.tiktok.models.gifts;
import lombok.Data;
@Data
public class DefaultFormat
{
public class DefaultFormat {
private boolean bold;
private String color;
private int font_size;

View File

@@ -5,8 +5,7 @@ import lombok.Data;
import java.util.List;
@Data
public class DisplayText
{
public class DisplayText {
private DefaultFormat default_format;
private String default_pattern;
private String key;

View File

@@ -5,8 +5,7 @@ import lombok.Data;
import java.util.List;
@Data
public class GiftLabelIcon
{
public class GiftLabelIcon {
private String avg_color;
private int height;
private int image_type;

View File

@@ -6,8 +6,7 @@ import java.util.List;
@Data
public class GiftPanelBanner
{
public class GiftPanelBanner {
private List<Object> bg_color_values;
private DisplayText display_text;
private LeftIcon left_icon;

View File

@@ -5,8 +5,7 @@ import lombok.Data;
import java.util.List;
@Data
public class Image
{
public class Image {
private String avg_color;
private int height;
private int image_type;

View File

@@ -5,8 +5,7 @@ import lombok.Data;
import java.util.List;
@Data
public class LeftIcon
{
public class LeftIcon {
private String avg_color;
private int height;
private int image_type;

View File

@@ -1,6 +1,9 @@
package io.github.jwdeveloper.tiktok.models.gifts;
import lombok.Data;
@Data
public class LockInfo
{
public int lock_type;
private int lock_type;
}

View File

@@ -5,7 +5,7 @@ import lombok.Data;
import java.util.List;
@Data
public class TikTokGift
public class TikTokGiftInfo
{
private int action_type;
private int app_id;

View File

@@ -0,0 +1,38 @@
package io.github.jwdeveloper.tiktok.utils;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveException;
public class CancelationToken
{
private boolean isCanceled =false;
public void cancel()
{
isCanceled =true;
}
public boolean isCancel()
{
return isCanceled;
}
public void throwIfCancel()
{
if(!isCanceled)
{
return;
}
throw new TikTokLiveException("Token requested cancelation");
}
public boolean isNotCancel()
{
return !isCancel();
}
public static CancelationToken create()
{
return new CancelationToken();
}
}

View File

@@ -186,7 +186,7 @@ message Badge {
message BadgeComplex {
uint32 data1 = 1;
string imageUrl = 2;
//string imageUrl = 2; Protocol message had invalid UTF-8
string data = 4;
DataContainer detail1 = 5;
string data2 = 6;
@@ -377,7 +377,7 @@ message RankTextMessage {
// Links to Image-Files on the TikTok CDN
message Picture {
repeated string urls = 1; // Usually has 3 different urls with different sizes/extensions
string prefix = 2; // uri
// string prefix = 2; // uri not working
uint32 data1 = 3;
uint32 data2 = 4;
string color = 5;
@@ -414,13 +414,16 @@ message User {
Picture profilePicture = 9; // Avatar
Picture picture720 = 10; // 720p
Picture picture1080 = 11; // 1080p
uint32 data2 = 15;
uint64 data3 = 16;
int32 status = 15;
int64 createTime = 16;
int64 modifyTime = 17;
int32 secret = 18;
string shareQrcodeUri = 19;
repeated Picture additionalPictures = 21;
FollowerData followerData = 22;
string userString1 = 23;
// string userString1 = 23;
UserRanking userRank1 = 25;
string userString2 = 32;
// string userString2 = 32;
uint64 data4 = 37;
string uniqueId = 38; // @-ID for user
string data5 = 46;
@@ -681,7 +684,7 @@ message WebcastGiftMessage {
uint32 data11 = 34;
message GiftData1 {
string data1 = 1;
// string data1 = 1; not working
uint32 data2 = 2;
uint32 data3 = 3;
}
@@ -749,8 +752,7 @@ message WebcastHourlyRankMessage {
// Message related to Chat-moderation?
message WebcastImDeleteMessage {
MessageHeader header = 1;
string data1 = 2;
string data2 = 3;
bytes data = 3;
}
message WebcastInRoomBannerMessage {

View File

@@ -5,7 +5,7 @@
<parent>
<artifactId>TikTokLiveJava</artifactId>
<groupId>io.github.jwdeveloper.tiktok</groupId>
<version>1.0.0</version>
<version>0.0.14-Release</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -29,6 +29,23 @@
<artifactId>protobuf-java</artifactId>
<version>3.24.1</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>2.0.7</version>
</dependency>
<dependency>
<groupId>org.java-websocket</groupId>
<artifactId>Java-WebSocket</artifactId>
<version>1.5.4</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>2.0.7</version>
<scope>runtime</scope>
</dependency>
</dependencies>

View File

@@ -1,45 +1,47 @@
package io.github.jwdeveloper.tiktok;
import io.github.jwdeveloper.tiktok.events.objects.TikTokGift;
import io.github.jwdeveloper.tiktok.http.TikTokApiService;
import io.github.jwdeveloper.tiktok.live.GiftManager;
import io.github.jwdeveloper.tiktok.messages.WebcastGiftMessage;
import io.github.jwdeveloper.tiktok.models.GiftId;
import io.github.jwdeveloper.tiktok.models.gifts.TikTokGiftInfo;
import lombok.Getter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
public class TikTokGiftManager {
private Logger logger;
private ClientSettings clientSettings;
private TikTokApiService apiService;
private Map<Integer, TikTokGift> gifts;
public class TikTokGiftManager implements GiftManager {
@Getter
private Map<GiftId, TikTokGift> activeGifts;
private final Map<Integer, TikTokGiftInfo> giftsInfo;
@Getter
private final Map<GiftId, TikTokGift> activeGifts;
public TikTokGiftManager(Logger logger, TikTokApiService apiService, ClientSettings clientSettings) {
this.logger = logger;
this.clientSettings = clientSettings;
this.apiService = apiService;
this.gifts = new HashMap<>();
public TikTokGiftManager() {
giftsInfo = new HashMap<>();
activeGifts = new HashMap<>();
}
public void loadGifts() {
if (!clientSettings.isDownloadGiftInfo()) {
return;
}
logger.info("Fetching gifts");
//TODO gifts =apiService.fetchAvailableGifts();
public TikTokGift updateActiveGift(WebcastGiftMessage giftMessage) {
var giftId = new GiftId(giftMessage.getGiftId(), giftMessage.getSender().getUniqueId());
if (activeGifts.containsKey(giftId)) {
var gift = activeGifts.get(giftId);
gift.setAmount(giftMessage.getAmount());
} else {
var newGift = new TikTokGift(giftMessage);
activeGifts.put(giftId, newGift);
}
public List<TikTokGift> getGifts()
{
return gifts.values().stream().toList();
var gift = activeGifts.get(giftId);
if (giftMessage.getRepeatEnd()) {
gift.setStreakFinished(true);
activeGifts.remove(giftId);
}
return gift;
}
public void loadGifsInfo(Map<Integer, TikTokGiftInfo> gifts) {
this.giftsInfo.putAll(gifts);
}
}

View File

@@ -8,6 +8,4 @@ public class TikTokLive
{
return new TikTokLiveClientBuilder(userName);
}
}

View File

@@ -1,53 +1,70 @@
package io.github.jwdeveloper.tiktok;
import io.github.jwdeveloper.tiktok.events.messages.TikTokDisconnectedEvent;
import io.github.jwdeveloper.tiktok.events.messages.TikTokErrorEvent;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveException;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveOfflineHostException;
import io.github.jwdeveloper.tiktok.handlers.TikTokEventHandler;
import io.github.jwdeveloper.tiktok.http.TikTokApiService;
import io.github.jwdeveloper.tiktok.live.ConnectionState;
import io.github.jwdeveloper.tiktok.live.GiftManager;
import io.github.jwdeveloper.tiktok.live.LiveClient;
import io.github.jwdeveloper.tiktok.live.LiveRoomInfo;
import io.github.jwdeveloper.tiktok.live.TikTokRoomInfo;
import io.github.jwdeveloper.tiktok.websocket.TikTokWebsocketClient;
import io.github.jwdeveloper.tiktok.websocket.TikTokWebSocketClient;
import java.util.logging.Logger;
public class TikTokLiveClient implements LiveClient {
private final TikTokRoomInfo meta;
private final TikTokGiftManager giftManager;
private final TikTokRoomInfo liveRoomInfo;
private final TikTokGiftManager tikTokGiftManager;
private final TikTokApiService apiClient;
private final TikTokWebsocketClient webSocketClient;
private final TikTokWebSocketClient webSocketClient;
private final TikTokEventHandler tikTokEventHandler;
private final ClientSettings clientSettings;
private final Logger logger;
public TikTokLiveClient(TikTokRoomInfo tikTokLiveMeta,
TikTokApiService tikTokApiService,
TikTokWebsocketClient webSocketClient,
TikTokWebSocketClient webSocketClient,
TikTokGiftManager tikTokGiftManager,
TikTokEventHandler tikTokEventHandler,
ClientSettings clientSettings,
Logger logger) {
this.meta = tikTokLiveMeta;
this.giftManager = tikTokGiftManager;
this.liveRoomInfo = tikTokLiveMeta;
this.tikTokGiftManager = tikTokGiftManager;
this.apiClient = tikTokApiService;
this.webSocketClient = webSocketClient;
this.logger = logger;
this.tikTokEventHandler = tikTokEventHandler;
this.clientSettings = clientSettings;
this.logger = logger;
}
public void connect() {
try {
tryConnect();
} catch (Exception e) {
e.printStackTrace();
}
catch (TikTokLiveException e)
{
setState(ConnectionState.DISCONNECTED);
tikTokEventHandler.publish(this, new TikTokErrorEvent(e));
tikTokEventHandler.publish(this, new TikTokDisconnectedEvent());
if(e instanceof TikTokLiveOfflineHostException && clientSettings.isRetryOnConnectionFailure())
{
try {
Thread.sleep(clientSettings.getRetryConnectionTimeout().toMillis());
}
catch (Exception ignored){}
logger.info("Reconnecting");
this.connect();
}
throw e;
}
}
public void disconnect() {
if (!meta.hasConnectionState(ConnectionState.CONNECTED)) {
if (!liveRoomInfo.hasConnectionState(ConnectionState.CONNECTED)) {
return;
}
webSocketClient.stop();
@@ -55,40 +72,48 @@ public class TikTokLiveClient implements LiveClient {
}
public void tryConnect() {
if (meta.hasConnectionState(ConnectionState.CONNECTED))
throw new RuntimeException("Already connected");
if (meta.hasConnectionState(ConnectionState.CONNECTING))
throw new RuntimeException("Already connecting");
if (liveRoomInfo.hasConnectionState(ConnectionState.CONNECTED))
throw new TikTokLiveException("Already connected");
if (liveRoomInfo.hasConnectionState(ConnectionState.CONNECTING))
throw new TikTokLiveException("Already connecting");
logger.info("Connecting");
setState(ConnectionState.CONNECTING);
var roomId = apiClient.fetchRoomId(meta.getUserName());
meta.setRoomId(roomId);
var roomId = apiClient.fetchRoomId(liveRoomInfo.getUserName());
liveRoomInfo.setRoomId(roomId);
var roomData = apiClient.fetchRoomInfo();
if (roomData.getStatus() == 0 || roomData.getStatus() == 4)
{
throw new TikTokLiveException("LiveStream for HostID could not be found. Is the Host online?");
if (roomData.getStatus() == 0 || roomData.getStatus() == 4) {
throw new TikTokLiveOfflineHostException("LiveStream for HostID could not be found. Is the Host online?");
}
// giftManager.loadGifts();
if (clientSettings.isDownloadGiftInfo())
{
logger.info("Fetch Gift info");
var gifts = apiClient.fetchAvailableGifts();
tikTokGiftManager.loadGifsInfo(gifts);
}
var clientData = apiClient.fetchClientData();
webSocketClient.start(clientData);
webSocketClient.start(clientData, this);
setState(ConnectionState.CONNECTED);
}
public LiveRoomInfo getRoomInfo() {
return meta;
return liveRoomInfo;
}
@Override
public GiftManager getGiftManager() {
return tikTokGiftManager;
}
private void setState(ConnectionState connectionState) {
logger.info("TikTokLive client state: " + connectionState.name());
meta.setConnectionState(connectionState);
liveRoomInfo.setConnectionState(connectionState);
}

View File

@@ -2,114 +2,88 @@ package io.github.jwdeveloper.tiktok;
import io.github.jwdeveloper.tiktok.events.TikTokEvent;
import io.github.jwdeveloper.tiktok.events.TikTokEventBuilder;
import io.github.jwdeveloper.tiktok.handlers.TikTokEventHandler;
import io.github.jwdeveloper.tiktok.events.TikTokEventConsumer;
import io.github.jwdeveloper.tiktok.events.messages.*;
import io.github.jwdeveloper.tiktok.handlers.WebResponseHandler;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveException;
import io.github.jwdeveloper.tiktok.handlers.TikTokEventHandler;
import io.github.jwdeveloper.tiktok.handlers.TikTokMessageHandlerRegistration;
import io.github.jwdeveloper.tiktok.http.TikTokApiService;
import io.github.jwdeveloper.tiktok.http.TikTokCookieJar;
import io.github.jwdeveloper.tiktok.http.TikTokHttpApiClient;
import io.github.jwdeveloper.tiktok.http.TikTokHttpRequestFactory;
import io.github.jwdeveloper.tiktok.live.LiveClient;
import io.github.jwdeveloper.tiktok.live.TikTokRoomInfo;
import io.github.jwdeveloper.tiktok.websocket.TikTokWebsocketClient;
import io.github.jwdeveloper.tiktok.utils.CancelationToken;
import io.github.jwdeveloper.tiktok.websocket.TikTokWebSocketClient;
import java.time.Duration;
import java.util.Map;
import java.util.function.Consumer;
import java.util.logging.Level;
import java.util.logging.Logger;
public class TikTokLiveClientBuilder implements TikTokEventBuilder<TikTokLiveClientBuilder> {
private String userName;
private final ClientSettings clientSettings;
private Map<String, Object> clientParameters;
private final Logger logger;
private final TikTokEventHandler tikTokEventHandler;
public TikTokLiveClientBuilder(String userName) {
this.tikTokEventHandler = new TikTokEventHandler();
this.userName = userName;
this.clientSettings = Constants.DefaultClientSettings();
this.clientParameters = Constants.DefaultClientParams();
this.clientSettings.setHostName(userName);
this.logger = Logger.getLogger(TikTokLive.class.getName());
}
public TikTokLiveClientBuilder clientSettings(Consumer<ClientSettings> consumer) {
public TikTokLiveClientBuilder configure(Consumer<ClientSettings> consumer) {
consumer.accept(clientSettings);
return this;
}
public TikTokLiveClientBuilder hostUserName(String userName) {
this.userName = userName;
return this;
}
public TikTokLiveClientBuilder clientParameters(Map<String, Object> clientParameters) {
this.clientParameters = clientParameters;
return this;
}
public TikTokLiveClientBuilder addClientParameters(String key, Object value) {
this.clientParameters.put(key, value);
return this;
}
private void validate() {
if (clientSettings.getTimeout() == null) {
clientSettings.setTimeout(Duration.ofSeconds(Constants.DEFAULT_TIMEOUT));
}
if (clientSettings.getPollingInterval() == null) {
clientSettings.setPollingInterval(Duration.ofSeconds(Constants.DEFAULT_POLLTIME));
}
if (clientSettings.getClientLanguage() == null || clientSettings.getClientLanguage().equals("")) {
clientSettings.setClientLanguage(Constants.DefaultClientSettings().getClientLanguage());
}
if (clientSettings.getSocketBufferSize() < 500_000) {
clientSettings.setSocketBufferSize(Constants.DefaultClientSettings().getSocketBufferSize());
if (clientSettings.getHostName() == null || clientSettings.getHostName().equals("")) {
throw new TikTokLiveException("HostName can not be null");
}
var params = clientSettings.getClientParameters();
params.put("app_language", clientSettings.getClientLanguage());
params.put("webcast_language", clientSettings.getClientLanguage());
if (userName == null || userName.equals("")) {
throw new RuntimeException("UserName can not be null");
logger.setLevel(clientSettings.getLogLevel());
if(clientSettings.isPrintToConsole() && clientSettings.getLogLevel() == Level.OFF)
{
logger.setLevel(Level.ALL);
}
if (clientParameters == null) {
clientParameters = Constants.DefaultClientParams();
}
clientParameters.put("app_language", clientSettings.getClientLanguage());
clientParameters.put("webcast_language", clientSettings.getClientLanguage());
}
public LiveClient build() {
validate();
var meta = new TikTokRoomInfo();
meta.setUserName(userName);
var tiktokRoomInfo = new TikTokRoomInfo();
tiktokRoomInfo.setUserName(clientSettings.getHostName());
var cookieJar = new TikTokCookieJar();
var requestFactory = new TikTokHttpRequestFactory(cookieJar);
var apiClient = new TikTokHttpApiClient(cookieJar, clientSettings, requestFactory);
var apiService = new TikTokApiService(apiClient, logger, clientParameters);
var giftManager = new TikTokGiftManager(logger, apiService, clientSettings);
var webResponseHandler = new WebResponseHandler(tikTokEventHandler,giftManager);
var webSocketClient = new TikTokWebsocketClient(logger,
var apiClient = new TikTokHttpApiClient(cookieJar, requestFactory);
var apiService = new TikTokApiService(apiClient, logger, clientSettings);
var giftManager = new TikTokGiftManager();
var webResponseHandler = new TikTokMessageHandlerRegistration(tikTokEventHandler, clientSettings, logger, giftManager, tiktokRoomInfo);
var webSocketClient = new TikTokWebSocketClient(logger,
cookieJar,
clientParameters,
requestFactory,
clientSettings,
webResponseHandler,
tikTokEventHandler);
return new TikTokLiveClient(meta, apiService, webSocketClient, giftManager, tikTokEventHandler, logger);
return new TikTokLiveClient(tiktokRoomInfo, apiService, webSocketClient, giftManager, tikTokEventHandler, clientSettings, logger);
}
public LiveClient buildAndRun() {
@@ -118,218 +92,243 @@ public class TikTokLiveClientBuilder implements TikTokEventBuilder<TikTokLiveCli
return client;
}
public TikTokLiveClientBuilder onUnhandledSocial(Consumer<TikTokUnhandledSocialEvent> event) {
public TikTokLiveClientBuilder onUnhandledSocial(
TikTokEventConsumer<TikTokUnhandledSocialEvent> event) {
tikTokEventHandler.subscribe(TikTokUnhandledSocialEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onLinkMicFanTicket(Consumer<TikTokLinkMicFanTicketEvent> event) {
public TikTokLiveClientBuilder onLinkMicFanTicket(
TikTokEventConsumer<TikTokLinkMicFanTicketEvent> event) {
tikTokEventHandler.subscribe(TikTokLinkMicFanTicketEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onEnvelope(Consumer<TikTokEnvelopeEvent> event) {
public TikTokLiveClientBuilder onEnvelope(TikTokEventConsumer<TikTokEnvelopeEvent> event) {
tikTokEventHandler.subscribe(TikTokEnvelopeEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onShopMessage(Consumer<TikTokShopMessageEvent> event) {
public TikTokLiveClientBuilder onShopMessage(TikTokEventConsumer<TikTokShopMessageEvent> event) {
tikTokEventHandler.subscribe(TikTokShopMessageEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onDetectMessage(Consumer<TikTokDetectMessageEvent> event) {
public TikTokLiveClientBuilder onDetectMessage(
TikTokEventConsumer<TikTokDetectMessageEvent> event) {
tikTokEventHandler.subscribe(TikTokDetectMessageEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onLinkLayerMessage(Consumer<TikTokLinkLayerMessageEvent> event) {
public TikTokLiveClientBuilder onLinkLayerMessage(
TikTokEventConsumer<TikTokLinkLayerMessageEvent> event) {
tikTokEventHandler.subscribe(TikTokLinkLayerMessageEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onConnected(Consumer<TikTokConnectedEvent> event) {
public TikTokLiveClientBuilder onConnected(TikTokEventConsumer<TikTokConnectedEvent> event) {
tikTokEventHandler.subscribe(TikTokConnectedEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onCaption(Consumer<TikTokCaptionEvent> event) {
public TikTokLiveClientBuilder onCaption(TikTokEventConsumer<TikTokCaptionEvent> event) {
tikTokEventHandler.subscribe(TikTokCaptionEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onQuestion(Consumer<TikTokQuestionEvent> event) {
public TikTokLiveClientBuilder onQuestion(TikTokEventConsumer<TikTokQuestionEvent> event) {
tikTokEventHandler.subscribe(TikTokQuestionEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onRoomPinMessage(Consumer<TikTokRoomPinMessageEvent> event) {
public TikTokLiveClientBuilder onRoomPinMessage(
TikTokEventConsumer<TikTokRoomPinMessageEvent> event) {
tikTokEventHandler.subscribe(TikTokRoomPinMessageEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onRoomMessage(Consumer<TikTokRoomMessageEvent> event) {
public TikTokLiveClientBuilder onRoomMessage(TikTokEventConsumer<TikTokRoomMessageEvent> event) {
tikTokEventHandler.subscribe(TikTokRoomMessageEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onLivePaused(Consumer<TikTokLivePausedEvent> event) {
public TikTokLiveClientBuilder onLivePaused(TikTokEventConsumer<TikTokLivePausedEvent> event) {
tikTokEventHandler.subscribe(TikTokLivePausedEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onLike(Consumer<TikTokLikeEvent> event) {
public TikTokLiveClientBuilder onLike(TikTokEventConsumer<TikTokLikeEvent> event) {
tikTokEventHandler.subscribe(TikTokLikeEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onLinkMessage(Consumer<TikTokLinkMessageEvent> event) {
public TikTokLiveClientBuilder onLinkMessage(TikTokEventConsumer<TikTokLinkMessageEvent> event) {
tikTokEventHandler.subscribe(TikTokLinkMessageEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onBarrageMessage(Consumer<TikTokBarrageMessageEvent> event) {
public TikTokLiveClientBuilder onBarrageMessage(
TikTokEventConsumer<TikTokBarrageMessageEvent> event) {
tikTokEventHandler.subscribe(TikTokBarrageMessageEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onGiftMessage(Consumer<TikTokGiftMessageEvent> event) {
public TikTokLiveClientBuilder onGiftMessage(TikTokEventConsumer<TikTokGiftMessageEvent> event) {
tikTokEventHandler.subscribe(TikTokGiftMessageEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onLinkMicArmies(Consumer<TikTokLinkMicArmiesEvent> event) {
public TikTokLiveClientBuilder onLinkMicArmies(
TikTokEventConsumer<TikTokLinkMicArmiesEvent> event) {
tikTokEventHandler.subscribe(TikTokLinkMicArmiesEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onEmote(Consumer<TikTokEmoteEvent> event) {
public TikTokLiveClientBuilder onEmote(TikTokEventConsumer<TikTokEmoteEvent> event) {
tikTokEventHandler.subscribe(TikTokEmoteEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onUnauthorizedMember(
Consumer<TikTokUnauthorizedMemberEvent> event) {
TikTokEventConsumer<TikTokUnauthorizedMemberEvent> event) {
tikTokEventHandler.subscribe(TikTokUnauthorizedMemberEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onInRoomBanner(Consumer<TikTokInRoomBannerEvent> event) {
public TikTokLiveClientBuilder onInRoomBanner(
TikTokEventConsumer<TikTokInRoomBannerEvent> event) {
tikTokEventHandler.subscribe(TikTokInRoomBannerEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onLinkMicMethod(Consumer<TikTokLinkMicMethodEvent> event) {
public TikTokLiveClientBuilder onLinkMicMethod(
TikTokEventConsumer<TikTokLinkMicMethodEvent> event) {
tikTokEventHandler.subscribe(TikTokLinkMicMethodEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onSubscribe(Consumer<TikTokSubscribeEvent> event) {
public TikTokLiveClientBuilder onSubscribe(TikTokEventConsumer<TikTokSubscribeEvent> event) {
tikTokEventHandler.subscribe(TikTokSubscribeEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onPollMessage(Consumer<TikTokPollMessageEvent> event) {
public TikTokLiveClientBuilder onPollMessage(TikTokEventConsumer<TikTokPollMessageEvent> event) {
tikTokEventHandler.subscribe(TikTokPollMessageEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onFollow(Consumer<TikTokFollowEvent> event) {
public TikTokLiveClientBuilder onFollow(TikTokEventConsumer<TikTokFollowEvent> event) {
tikTokEventHandler.subscribe(TikTokFollowEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onRoomViewerData(Consumer<TikTokRoomViewerDataEvent> event) {
public TikTokLiveClientBuilder onRoomViewerData(
TikTokEventConsumer<TikTokRoomViewerDataEvent> event) {
tikTokEventHandler.subscribe(TikTokRoomViewerDataEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onGoalUpdate(Consumer<TikTokGoalUpdateEvent> event) {
public TikTokLiveClientBuilder onGoalUpdate(TikTokEventConsumer<TikTokGoalUpdateEvent> event) {
tikTokEventHandler.subscribe(TikTokGoalUpdateEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onComment(Consumer<TikTokCommentEvent> event) {
public TikTokLiveClientBuilder onComment(TikTokEventConsumer<TikTokCommentEvent> event) {
tikTokEventHandler.subscribe(TikTokCommentEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onRankUpdate(Consumer<TikTokRankUpdateEvent> event) {
public TikTokLiveClientBuilder onRankUpdate(TikTokEventConsumer<TikTokRankUpdateEvent> event) {
tikTokEventHandler.subscribe(TikTokRankUpdateEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onIMDelete(Consumer<TikTokIMDeleteEvent> event) {
public TikTokLiveClientBuilder onIMDelete(TikTokEventConsumer<TikTokIMDeleteEvent> event) {
tikTokEventHandler.subscribe(TikTokIMDeleteEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onLiveEnded(Consumer<TikTokLiveEndedEvent> event) {
public TikTokLiveClientBuilder onLiveEnded(TikTokEventConsumer<TikTokLiveEndedEvent> event) {
tikTokEventHandler.subscribe(TikTokLiveEndedEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onError(Consumer<TikTokErrorEvent> event) {
public TikTokLiveClientBuilder onError(TikTokEventConsumer<TikTokErrorEvent> event) {
tikTokEventHandler.subscribe(TikTokErrorEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onUnhandled(Consumer<TikTokUnhandledEvent> event) {
public TikTokLiveClientBuilder onUnhandled(TikTokEventConsumer<TikTokUnhandledEvent> event) {
tikTokEventHandler.subscribe(TikTokUnhandledEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onJoin(Consumer<TikTokJoinEvent> event) {
public TikTokLiveClientBuilder onJoin(TikTokEventConsumer<TikTokJoinEvent> event) {
tikTokEventHandler.subscribe(TikTokJoinEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onRankText(Consumer<TikTokRankTextEvent> event) {
public TikTokLiveClientBuilder onRankText(TikTokEventConsumer<TikTokRankTextEvent> event) {
tikTokEventHandler.subscribe(TikTokRankTextEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onShare(Consumer<TikTokShareEvent> event) {
public TikTokLiveClientBuilder onShare(TikTokEventConsumer<TikTokShareEvent> event) {
tikTokEventHandler.subscribe(TikTokShareEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onUnhandledMember(Consumer<TikTokUnhandledMemberEvent> event) {
public TikTokLiveClientBuilder onUnhandledMember(
TikTokEventConsumer<TikTokUnhandledMemberEvent> event) {
tikTokEventHandler.subscribe(TikTokUnhandledMemberEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onSubNotify(Consumer<TikTokSubNotifyEvent> event) {
public TikTokLiveClientBuilder onSubNotify(TikTokEventConsumer<TikTokSubNotifyEvent> event) {
tikTokEventHandler.subscribe(TikTokSubNotifyEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onLinkMicBattle(Consumer<TikTokLinkMicBattleEvent> event) {
public TikTokLiveClientBuilder onLinkMicBattle(
TikTokEventConsumer<TikTokLinkMicBattleEvent> event) {
tikTokEventHandler.subscribe(TikTokLinkMicBattleEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onDisconnected(Consumer<TikTokDisconnectedEvent> event) {
public TikTokLiveClientBuilder onDisconnected(
TikTokEventConsumer<TikTokDisconnectedEvent> event) {
tikTokEventHandler.subscribe(TikTokDisconnectedEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onGiftBroadcast(Consumer<TikTokGiftBroadcastEvent> event) {
public TikTokLiveClientBuilder onGiftBroadcast(
TikTokEventConsumer<TikTokGiftBroadcastEvent> event) {
tikTokEventHandler.subscribe(TikTokGiftBroadcastEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onUnhandledControl(Consumer<TikTokUnhandledControlEvent> event) {
public TikTokLiveClientBuilder onUnhandledControl(
TikTokEventConsumer<TikTokUnhandledControlEvent> event) {
tikTokEventHandler.subscribe(TikTokUnhandledControlEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onEvent(Consumer<TikTokEvent> event) {
public TikTokLiveClientBuilder onEvent(TikTokEventConsumer<TikTokEvent> event) {
tikTokEventHandler.subscribe(TikTokEvent.class, event);
return this;
}
@Override
public TikTokLiveClientBuilder onWebsocketMessage(TikTokEventConsumer<TikTokWebsocketMessageEvent> event) {
tikTokEventHandler.subscribe(TikTokWebsocketMessageEvent.class, event);
return this;
}
}

View File

@@ -1,5 +1,7 @@
package io.github.jwdeveloper.tiktok.live;
package io.github.jwdeveloper.tiktok;
import io.github.jwdeveloper.tiktok.live.ConnectionState;
import io.github.jwdeveloper.tiktok.live.LiveRoomInfo;
import lombok.Data;
@Data

View File

@@ -1,40 +1,38 @@
package io.github.jwdeveloper.tiktok.handlers;
import io.github.jwdeveloper.tiktok.TikTokLiveClient;
import io.github.jwdeveloper.tiktok.events.TikTokEvent;
import io.github.jwdeveloper.tiktok.events.TikTokEventConsumer;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Consumer;
public class TikTokEventHandler {
private final Map<String, Consumer> events;
private final Map<String, TikTokEventConsumer> events;
public TikTokEventHandler()
{
public TikTokEventHandler() {
events = new HashMap<>();
}
public void publish(TikTokEvent tikTokEvent)
{
if(events.containsKey(TikTokEvent.class.getSimpleName()))
{
public void publish(TikTokLiveClient tikTokLiveClient, TikTokEvent tikTokEvent) {
if (events.containsKey(TikTokEvent.class.getSimpleName())) {
var handler = events.get(TikTokEvent.class.getSimpleName());
handler.accept(tikTokEvent);
handler.onEvent(tikTokLiveClient, tikTokEvent);
}
var name = tikTokEvent.getClass().getSimpleName();
if(!events.containsKey(name))
{
if (!events.containsKey(name)) {
return;
}
var handler = events.get(name);
handler.accept(tikTokEvent);
handler.onEvent(tikTokLiveClient, tikTokEvent);
}
public <T extends TikTokEvent> void subscribe(Class<?> clazz, Consumer<T> event)
{
public <T extends TikTokEvent> void subscribe(Class<?> clazz, TikTokEventConsumer<T> event) {
events.put(clazz.getSimpleName(), event);
}
public <T extends TikTokEvent> void unsubscribe(Class<?> clazz) {
events.remove(clazz);
}
}

View File

@@ -1,12 +1,99 @@
package io.github.jwdeveloper.tiktok.handlers;
import com.google.protobuf.ByteString;
import io.github.jwdeveloper.tiktok.ClientSettings;
import io.github.jwdeveloper.tiktok.TikTokLiveClient;
import io.github.jwdeveloper.tiktok.events.TikTokEvent;
import io.github.jwdeveloper.tiktok.events.messages.TikTokErrorEvent;
import io.github.jwdeveloper.tiktok.events.messages.TikTokWebsocketMessageEvent;
import io.github.jwdeveloper.tiktok.events.messages.TikTokUnhandledEvent;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveMessageException;
import io.github.jwdeveloper.tiktok.exceptions.TikTokMessageMappingException;
import io.github.jwdeveloper.tiktok.messages.WebcastResponse;
public interface TikTokMessageHandler<T>
{
Class<T> getHandleClazz();
import java.util.Arrays;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
import java.util.logging.Logger;
TikTokEvent handle(WebcastResponse.Message message) throws Exception;
public abstract class TikTokMessageHandler {
private final Map<String, io.github.jwdeveloper.tiktok.handler.TikTokMessageHandler> handlers;
private final TikTokEventHandler tikTokEventHandler;
private final ClientSettings clientSettings;
protected final Logger logger;
public TikTokMessageHandler(TikTokEventHandler tikTokEventHandler,ClientSettings clientSettings, Logger logger) {
handlers = new HashMap<>();
this.tikTokEventHandler = tikTokEventHandler;
this.clientSettings = clientSettings;
this.logger = logger;
init();
}
public abstract void init();
public void register(Class<?> clazz, Function<WebcastResponse.Message, TikTokEvent> func) {
handlers.put(clazz.getSimpleName(), func::apply);
}
public void register(Class<?> input, Class<?> output) {
register(input, (e) -> mapMessageToEvent(input, output, e));
}
public void handle(TikTokLiveClient client, WebcastResponse webcastResponse) {
for (var message : webcastResponse.getMessagesList()) {
try
{
if(clientSettings.isPrintMessageData())
{
var type= message.getType();
var base64 = Base64.getEncoder().encodeToString(message.getBinary().toByteArray());
logger.info(type+": \n "+base64);
}
handleSingleMessage(client, message);
} catch (Exception e) {
var exception = new TikTokLiveMessageException(message, webcastResponse, e);
tikTokEventHandler.publish(client, new TikTokErrorEvent(exception));
}
}
}
private void handleSingleMessage(TikTokLiveClient client, WebcastResponse.Message message) throws Exception {
if (!handlers.containsKey(message.getType())) {
tikTokEventHandler.publish(client, new TikTokUnhandledEvent(message));
return;
}
var handler = handlers.get(message.getType());
var tiktokEvent = handler.handle(message);
tikTokEventHandler.publish(client, new TikTokWebsocketMessageEvent(tiktokEvent, message));
tikTokEventHandler.publish(client, tiktokEvent);
}
protected TikTokEvent mapMessageToEvent(Class<?> inputClazz, Class<?> outputClass, WebcastResponse.Message message) {
try {
var parseMethod = inputClazz.getDeclaredMethod("parseFrom", ByteString.class);
var deserialized = parseMethod.invoke(null, message.getBinary());
var constructors = Arrays.stream(outputClass.getConstructors())
.filter(ea -> Arrays.stream(ea.getParameterTypes())
.toList()
.contains(inputClazz))
.findFirst();
if(constructors.isEmpty())
{
throw new TikTokMessageMappingException(inputClazz, outputClass, "Unable to find constructor with input class type");
}
var tiktokEvent = constructors.get().newInstance(deserialized);
return (TikTokEvent) tiktokEvent;
} catch (Exception ex) {
throw new TikTokMessageMappingException(inputClazz, outputClass, ex);
}
}
}

View File

@@ -1,6 +1,8 @@
package io.github.jwdeveloper.tiktok.handlers;
import io.github.jwdeveloper.tiktok.ClientSettings;
import io.github.jwdeveloper.tiktok.TikTokGiftManager;
import io.github.jwdeveloper.tiktok.TikTokRoomInfo;
import io.github.jwdeveloper.tiktok.events.TikTokEvent;
import io.github.jwdeveloper.tiktok.events.messages.*;
import io.github.jwdeveloper.tiktok.events.objects.TikTokGift;
@@ -9,28 +11,35 @@ import io.github.jwdeveloper.tiktok.models.GiftId;
import io.github.jwdeveloper.tiktok.models.SocialTypes;
import lombok.SneakyThrows;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class WebResponseHandler extends WebResponseHandlerBase {
public class TikTokMessageHandlerRegistration extends TikTokMessageHandler {
private final TikTokGiftManager giftManager;
private final TikTokRoomInfo roomInfo;
public WebResponseHandler(TikTokEventHandler tikTokEventHandler, TikTokGiftManager giftManager) {
super(tikTokEventHandler);
public TikTokMessageHandlerRegistration(TikTokEventHandler tikTokEventHandler,
ClientSettings clientSettings,
Logger logger,
TikTokGiftManager giftManager,
TikTokRoomInfo roomInfo) {
super(tikTokEventHandler, clientSettings, logger);
this.giftManager = giftManager;
this.roomInfo = roomInfo;
}
@Override
public void init() {
//ConnectionEvents events
register(WebcastControlMessage.class, TikTokRoomMessageEvent.class);
register(SystemMessage.class, this::handleWebcastControlMessage);
register(WebcastControlMessage.class, this::handleWebcastControlMessage);
register(SystemMessage.class,TikTokRoomMessageEvent.class);
//Room status events
register(WebcastLiveIntroMessage.class, TikTokRoomMessageEvent.class);
register(WebcastRoomUserSeqMessage.class, TikTokRoomViewerDataEvent.class); //TODO update viewer count ViewerCount = userSeqMessage.ViewerCount;
register(WebcastRoomUserSeqMessage.class, this::handleRoomUserSeqMessage);
register(RoomMessage.class, TikTokRoomMessageEvent.class);
register(WebcastRoomMessage.class, TikTokRoomMessageEvent.class);
register(WebcastCaptionMessage.class, TikTokCaptionEvent.class);
@@ -74,9 +83,9 @@ public class WebResponseHandler extends WebResponseHandlerBase {
}
@SneakyThrows
private TikTokEvent handleWebcastControlMessage(WebcastResponse.Message msg)
{
private TikTokEvent handleWebcastControlMessage(WebcastResponse.Message msg) {
var message = WebcastControlMessage.parseFrom(msg.getBinary());
return switch (message.getAction()) {
case STREAM_PAUSED -> new TikTokLivePausedEvent();
@@ -88,29 +97,7 @@ public class WebResponseHandler extends WebResponseHandlerBase {
@SneakyThrows
private TikTokEvent handleGift(WebcastResponse.Message msg) {
var giftMessage = WebcastGiftMessage.parseFrom(msg.getBinary());
var giftId = new GiftId(giftMessage.getGiftId(), giftMessage.getSender().getUniqueId());
var activeGifts = giftManager.getActiveGifts();
if (activeGifts.containsKey(giftId)) {
// Debug.Log($"Updating Gift[{giftId.Gift}]Amount[{message.Amount}]");
var gift = activeGifts.get(giftId);
gift.setAmount(giftMessage.getAmount());
} else {
TikTokGift newGift = new TikTokGift(giftMessage);
activeGifts.put(giftId, newGift);
// Debug.Log($"New Gift[{giftId.Gift}]Amount[{message.Amount}]");
// RunEvent(OnGift, newGift);
}
if (giftMessage.getRepeatEnd()) {
//if (ShouldLog(LogLevel.Verbose))
// Debug.Log($"GiftStreak Ended: [{giftId.Gift}] Amount[{message.Amount}]")
var gift = activeGifts.get(giftId);
gift.setStreakFinished(true);
activeGifts.remove(gift);
}
// Debug.Log($"Handling GiftMessage");
giftManager.updateActiveGift(giftMessage);
return new TikTokGiftMessageEvent(giftMessage);
}
@@ -118,16 +105,18 @@ public class WebResponseHandler extends WebResponseHandlerBase {
private TikTokEvent handleSocialMedia(WebcastResponse.Message msg) {
var message = WebcastSocialMessage.parseFrom(msg.getBinary());
String type = message.getHeader().getSocialData().getType();
Pattern pattern = Pattern.compile("pm_mt_guidance_viewer_([0-9]+)_share");
Matcher matcher = pattern.matcher(type);
if (matcher.find()) {
var socialType = message.getHeader().getSocialData().getType();
var pattern = Pattern.compile("pm_mt_guidance_viewer_([0-9]+)_share");
var matcher = pattern.matcher(socialType);
if (matcher.find())
{
var value = matcher.group(0);
var number = Integer.parseInt(value);
return new TikTokShareEvent(message, number);
}
var socialType = message.getHeader().getSocialData().getType();
return switch (socialType) {
case SocialTypes.LikeType -> new TikTokLikeEvent(message);
case SocialTypes.FollowType -> new TikTokFollowEvent(message);
@@ -146,4 +135,11 @@ public class WebResponseHandler extends WebResponseHandlerBase {
default -> new TikTokUnhandledMemberEvent(message);
};
}
private TikTokEvent handleRoomUserSeqMessage(WebcastResponse.Message msg)
{
var event = (TikTokRoomViewerDataEvent)mapMessageToEvent(WebcastRoomUserSeqMessage.class, TikTokRoomViewerDataEvent.class, msg);
roomInfo.setViewersCount(event.getViewerCount());
return event;
}
}

View File

@@ -1,89 +0,0 @@
package io.github.jwdeveloper.tiktok.handlers;
import com.google.protobuf.ByteString;
import io.github.jwdeveloper.tiktok.events.TikTokEvent;
import io.github.jwdeveloper.tiktok.events.messages.TikTokErrorEvent;
import io.github.jwdeveloper.tiktok.events.messages.TikTokUnhandledEvent;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveException;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveMessageParsingException;
import io.github.jwdeveloper.tiktok.messages.WebcastResponse;
import java.util.Arrays;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
public abstract class WebResponseHandlerBase {
private final Map<String, TikTokMessageHandler> handlers;
private final TikTokEventHandler tikTokEventHandler;
public WebResponseHandlerBase(TikTokEventHandler tikTokEventHandler) {
handlers = new HashMap<>();
this.tikTokEventHandler = tikTokEventHandler;
init();
}
public abstract void init();
public void register(Class<?> input, Class<?> output) {
register(input, (e) ->
{
try {
var parseMethod = input.getDeclaredMethod("parseFrom", ByteString.class);
var deserialized = parseMethod.invoke(null, e.getBinary());
var constructors = Arrays.stream(output.getConstructors()).filter(ea -> Arrays.stream(ea.getParameterTypes()).toList().contains(input)).findFirst();
var tiktokEvent = constructors.get().newInstance(deserialized);
return (TikTokEvent)tiktokEvent;
} catch (Exception ex)
{
throw new TikTokLiveMessageParsingException("Unable to handle parsing from class: " + input.getSimpleName() + " to class " + output.getSimpleName(), ex);
}
});
}
public <T> void register(Class clazz, Function<WebcastResponse.Message, TikTokEvent> func) {
var haandler = new TikTokMessageHandler<T>() {
@Override
public Class<T> getHandleClazz() {
return clazz;
}
@Override
public TikTokEvent handle(WebcastResponse.Message message) throws Exception {
return func.apply(message);
}
};
handlers.put(haandler.getHandleClazz().getSimpleName(), haandler);
}
public void handle(WebcastResponse webcastResponse) {
for (var message : webcastResponse.getMessagesList()) {
try {
handleSingleMessage(message);
} catch (Exception e)
{
var decoded = Base64.getEncoder().encodeToString(message.getBinary().toByteArray());
var exception = new TikTokLiveException("Error whilst Handling Message. Stopping Client. Final Message: \n"+decoded, e);
tikTokEventHandler.publish(new TikTokErrorEvent(exception));
}
}
}
private void handleSingleMessage(WebcastResponse.Message message) throws Exception {
if (!handlers.containsKey(message.getType())) {
tikTokEventHandler.publish(new TikTokUnhandledEvent(message));
return;
}
var handler = handlers.get(message.getType());
var tiktokEvent = handler.handle(message);
tikTokEventHandler.publish(tiktokEvent);
}
}

View File

@@ -44,8 +44,8 @@ public class HttpUtils
builder.append("&");
}
final String encodedKey = URLEncoder.encode(param.getKey().toString(), StandardCharsets.UTF_8.toString());
final String encodedValue = URLEncoder.encode(param.getValue().toString(), StandardCharsets.UTF_8.toString());
final String encodedKey = URLEncoder.encode(param.getKey(), StandardCharsets.UTF_8);
final String encodedValue = URLEncoder.encode(param.getValue().toString(), StandardCharsets.UTF_8);
builder.append(encodedKey).append("=").append(encodedValue);
first = true;
}

View File

@@ -1,9 +1,11 @@
package io.github.jwdeveloper.tiktok.http;
import com.google.gson.Gson;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveException;
import io.github.jwdeveloper.tiktok.ClientSettings;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveOfflineHostException;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveRequestException;
import io.github.jwdeveloper.tiktok.live.LiveRoomMeta;
import io.github.jwdeveloper.tiktok.models.gifts.TikTokGift;
import io.github.jwdeveloper.tiktok.models.gifts.TikTokGiftInfo;
import io.github.jwdeveloper.tiktok.messages.WebcastResponse;
import java.util.HashMap;
@@ -15,12 +17,12 @@ import java.util.regex.Pattern;
public class TikTokApiService {
private final TikTokHttpApiClient apiClient;
private final Logger logger;
private final Map<String, Object> clientParams;
private final ClientSettings clientSettings;
public TikTokApiService(TikTokHttpApiClient apiClient, Logger logger, Map<String, Object> clientParams) {
public TikTokApiService(TikTokHttpApiClient apiClient, Logger logger, ClientSettings clientSettings) {
this.apiClient = apiClient;
this.logger = logger;
this.clientParams = clientParams;
this.clientSettings = clientSettings;
}
public String fetchRoomId(String userName) {
@@ -29,7 +31,7 @@ public class TikTokApiService {
try {
html = apiClient.GetLivestreamPage(userName);
} catch (Exception e) {
throw new RuntimeException("Failed to fetch room id from WebCast, see stacktrace for more info.", e);
throw new TikTokLiveRequestException("Failed to fetch room id from WebCast, see stacktrace for more info.", e);
}
Pattern firstPattern = Pattern.compile("room_id=([0-9]*)");
@@ -48,10 +50,10 @@ public class TikTokApiService {
}
if (id.isEmpty()) {
throw new TikTokLiveException("Unable to fetch room ID");
throw new TikTokLiveOfflineHostException("Unable to fetch room ID, live host could be offline or name is misspelled");
}
clientParams.put("room_id", id);
clientSettings.getClientParameters().put("room_id", id);
logger.info("RoomID -> "+id);
return id;
}
@@ -60,7 +62,7 @@ public class TikTokApiService {
public LiveRoomMeta fetchRoomInfo() {
logger.info("Fetch RoomInfo");
try {
var response = apiClient.GetJObjectFromWebcastAPI("room/info/", clientParams);
var response = apiClient.GetJObjectFromWebcastAPI("room/info/", clientSettings.getClientParameters());
if (!response.has("data")) {
return new LiveRoomMeta();
}
@@ -78,7 +80,7 @@ public class TikTokApiService {
logger.info("RoomInfo status -> "+info.getStatus());
return info;
} catch (Exception e) {
throw new TikTokLiveException("Failed to fetch room info from WebCast, see stacktrace for more info.", e);
throw new TikTokLiveRequestException("Failed to fetch room info from WebCast, see stacktrace for more info.", e);
}
}
@@ -86,20 +88,20 @@ public class TikTokApiService {
{
logger.info("Fetch ClientData");
try {
var response = apiClient.GetDeserializedMessage("im/fetch/", clientParams);
clientParams.put("cursor",response.getCursor());
clientParams.put("internal_ext", response.getAckIds());
var response = apiClient.GetDeserializedMessage("im/fetch/", clientSettings.getClientParameters());
clientSettings.getClientParameters().put("cursor",response.getCursor());
clientSettings.getClientParameters().put("internal_ext", response.getAckIds());
return response;
}
catch (Exception e)
{
throw new TikTokLiveException("Failed to fetch client data", e);
throw new TikTokLiveRequestException("Failed to fetch client data", e);
}
}
public Map<Integer, TikTokGift> fetchAvailableGifts() {
public Map<Integer, TikTokGiftInfo> fetchAvailableGifts() {
try {
var response = apiClient.GetJObjectFromWebcastAPI("gift/list/", clientParams);
var response = apiClient.GetJObjectFromWebcastAPI("gift/list/", clientSettings.getClientParameters());
if(!response.has("data"))
{
return new HashMap<>();
@@ -110,17 +112,17 @@ public class TikTokApiService {
return new HashMap<>();
}
var giftsJsonList = dataJson.get("gifts").getAsJsonArray();
var gifts = new HashMap<Integer, TikTokGift>();
var gifts = new HashMap<Integer, TikTokGiftInfo>();
var gson = new Gson();
for(var jsonGift : giftsJsonList)
{
var gift = gson.fromJson(jsonGift, TikTokGift.class);
var gift = gson.fromJson(jsonGift, TikTokGiftInfo.class);
logger.info("Found Available Gift "+ gift.getName()+ " with ID "+gift.getId());
gifts.put(gift.getId(),gift);
}
return gifts;
} catch (Exception e) {
throw new TikTokLiveException("Failed to fetch giftTokens from WebCast, see stacktrace for more info.", e);
throw new TikTokLiveRequestException("Failed to fetch giftTokens from WebCast, see stacktrace for more info.", e);
}
}
}

View File

@@ -4,7 +4,7 @@ import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import io.github.jwdeveloper.tiktok.ClientSettings;
import io.github.jwdeveloper.tiktok.Constants;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveException;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveRequestException;
import io.github.jwdeveloper.tiktok.messages.WebcastResponse;
import java.net.URI;
@@ -16,27 +16,23 @@ import java.util.Map;
import java.util.TreeMap;
public class TikTokHttpApiClient {
private final ClientSettings clientSettings;
private final TikTokHttpRequestFactory requestFactory;
private final TikTokCookieJar tikTokCookieJar;
public TikTokHttpApiClient(TikTokCookieJar tikTokCookieJar, ClientSettings clientSettings, TikTokHttpRequestFactory requestFactory) {
this.clientSettings = clientSettings;
public TikTokHttpApiClient(TikTokCookieJar tikTokCookieJar, TikTokHttpRequestFactory requestFactory) {
this.requestFactory = requestFactory;
this.tikTokCookieJar = tikTokCookieJar;
}
public String GetLivestreamPage(String userName) {
var url = Constants.TIKTOK_URL_WEB + "@" + userName + "/live/";
var get = getRequest(url, null, false);
var get = getRequest(url, null);
return get;
}
public JsonObject GetJObjectFromWebcastAPI(String path, Map<String, Object> parameters) {
var get = getRequest(Constants.TIKTOK_URL_WEBCAST + path, parameters, false);
var get = getRequest(Constants.TIKTOK_URL_WEBCAST + path, parameters);
var json = JsonParser.parseString(get);
var jsonObject = json.getAsJsonObject();
return jsonObject;
@@ -49,12 +45,11 @@ public class TikTokHttpApiClient {
}
catch (Exception e)
{
throw new TikTokLiveException("Unable to deserialize message: "+path,e);
throw new TikTokLiveRequestException("Unable to deserialize message: "+path,e);
}
}
private String getRequest(String url, Map<String, Object> parameters, boolean signURL) {
private String getRequest(String url, Map<String, Object> parameters) {
if (parameters == null) {
parameters = new HashMap<>();
}
@@ -86,7 +81,7 @@ public class TikTokHttpApiClient {
}
catch (Exception e)
{
throw new TikTokLiveException("unabel to send signature");
throw new TikTokLiveRequestException("Unable to send signature");
}
}
@@ -95,7 +90,7 @@ public class TikTokHttpApiClient {
var fullUrl = HttpUtils.parseParameters(url,parameters);
var singHeaders = new TreeMap<String,Object>();
singHeaders.put("client", "ttlive-net");
singHeaders.put("client", "ttlive-java");
singHeaders.put("uuc", 1);
singHeaders.put("url", fullUrl);
@@ -112,7 +107,7 @@ public class TikTokHttpApiClient {
requestFactory.setAgent(userAgent);
return signedUrl;
} catch (Exception e) {
throw new TikTokLiveException("Insufficent values have been supplied for signing. Likely due to an update. Post an issue on GitHub.", e);
throw new TikTokLiveRequestException("Insufficient values have been supplied for signing. Likely due to an update. Post an issue on GitHub.", e);
}
}

View File

@@ -2,10 +2,10 @@ package io.github.jwdeveloper.tiktok.http;
import io.github.jwdeveloper.tiktok.Constants;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveRequestException;
import lombok.SneakyThrows;
import java.net.CookieManager;
import java.net.ProxySelector;
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
@@ -18,24 +18,16 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class TikTokHttpRequestFactory implements TikTokHttpRequest
{
private CookieManager cookieManager;
private HttpClient client;
private Duration timeout;
private ProxySelector webProxy;
public class TikTokHttpRequestFactory implements TikTokHttpRequest {
private final CookieManager cookieManager;
private final Map<String, String> defaultHeaders;
private final TikTokCookieJar tikTokCookieJar;
private final HttpClient client;
private String query;
private Boolean sent;
private Map<String, String> defaultHeaders;
private TikTokCookieJar tikTokCookieJar;
public TikTokHttpRequestFactory(TikTokCookieJar tikTokCookieJar) {
cookieManager = new CookieManager();
this.tikTokCookieJar = tikTokCookieJar;
this.cookieManager = new CookieManager();
defaultHeaders = Constants.DefaultRequestHeaders();
client = HttpClient.newBuilder()
.cookieHandler(cookieManager)
@@ -43,8 +35,7 @@ public class TikTokHttpRequestFactory implements TikTokHttpRequest
.build();
}
public WebSocket.Builder openSocket()
{
public WebSocket.Builder openSocket() {
return client.newWebSocketBuilder();
}
@@ -52,10 +43,6 @@ public class TikTokHttpRequestFactory implements TikTokHttpRequest
public String Get(String url) {
var uri = URI.create(url);
var request = HttpRequest.newBuilder().GET();
for(var header : defaultHeaders.entrySet())
{
//request.setHeader(header.getKey(),header.getValue());
}
if (query != null) {
var baseUri = uri.toString();
var requestUri = URI.create(baseUri + "?" + query);
@@ -69,8 +56,7 @@ public class TikTokHttpRequestFactory implements TikTokHttpRequest
public String Post(String url, HttpRequest.BodyPublisher data) {
var uri = URI.create(url);
var request = HttpRequest.newBuilder().POST(data);
for(var header : defaultHeaders.entrySet())
{
for (var header : defaultHeaders.entrySet()) {
request.setHeader(header.getKey(), header.getValue());
}
if (query != null) {
@@ -81,14 +67,12 @@ public class TikTokHttpRequestFactory implements TikTokHttpRequest
return GetContent(request.build());
}
public TikTokHttpRequest setHeader(String key, String value)
{
public TikTokHttpRequest setHeader(String key, String value) {
defaultHeaders.put(key, value);
return this;
}
public TikTokHttpRequest setAgent( String value)
{
public TikTokHttpRequest setAgent(String value) {
defaultHeaders.put("User-Agent", value);
return this;
}
@@ -111,26 +95,18 @@ public class TikTokHttpRequestFactory implements TikTokHttpRequest
}
private String GetContent(HttpRequest request) throws Exception {
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
sent = true;
if (response.statusCode() == 404)
{
throw new RuntimeException("Request responded with 404 NOT_FOUND");
if (response.statusCode() == 404) {
throw new TikTokLiveRequestException("Request responded with 404 NOT_FOUND");
}
if(response.statusCode() != 200)
{
throw new RuntimeException("Request was unsuccessful "+response.statusCode());
if (response.statusCode() != 200) {
throw new TikTokLiveRequestException("Request was unsuccessful " + response.statusCode());
}
var cookies = response.headers().allValues("Set-Cookie");
for(var cookie : cookies)
{
for (var cookie : cookies) {
var split = cookie.split(";")[0].split("=");
var uri = request.uri();

View File

@@ -3,57 +3,49 @@ package io.github.jwdeveloper.tiktok.websocket;
import io.github.jwdeveloper.tiktok.ClientSettings;
import io.github.jwdeveloper.tiktok.Constants;
import io.github.jwdeveloper.tiktok.TikTokLiveClient;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveException;
import io.github.jwdeveloper.tiktok.handlers.TikTokEventHandler;
import io.github.jwdeveloper.tiktok.handlers.WebResponseHandler;
import io.github.jwdeveloper.tiktok.handlers.TikTokMessageHandlerRegistration;
import io.github.jwdeveloper.tiktok.http.HttpUtils;
import io.github.jwdeveloper.tiktok.http.TikTokCookieJar;
import io.github.jwdeveloper.tiktok.http.TikTokHttpRequestFactory;
import io.github.jwdeveloper.tiktok.messages.WebcastResponse;
import org.java_websocket.client.WebSocketClient;
import java.net.URI;
import java.net.http.WebSocket;
import java.nio.ByteBuffer;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
import java.util.logging.Logger;
public class TikTokWebsocketClient {
public class TikTokWebSocketClient {
private final Logger logger;
private final Map<String, Object> clientParams;
private final ClientSettings clientSettings;
private final TikTokCookieJar tikTokCookieJar;
private final TikTokHttpRequestFactory factory;
private final WebResponseHandler webResponseHandler;
private final TikTokMessageHandlerRegistration webResponseHandler;
private final TikTokEventHandler tikTokEventHandler;
private WebSocket webSocket;
private WebSocketClient webSocketClient;
private TikTokLiveClient tikTokLiveClient;
private TikTokWebSocketPingingTask pingingTask;
private boolean isConnected;
public TikTokWebsocketClient(Logger logger,
public TikTokWebSocketClient(Logger logger,
TikTokCookieJar tikTokCookieJar,
Map<String, Object> clientParams,
TikTokHttpRequestFactory factory,
ClientSettings clientSettings,
WebResponseHandler webResponseHandler,
TikTokMessageHandlerRegistration webResponseHandler,
TikTokEventHandler tikTokEventHandler) {
this.logger = logger;
this.clientParams = clientParams;
this.tikTokCookieJar = tikTokCookieJar;
this.clientSettings = clientSettings;
this.factory = factory;
this.webResponseHandler = webResponseHandler;
this.tikTokEventHandler = tikTokEventHandler;
isConnected = false;
}
public void start(WebcastResponse webcastResponse)
{
if(isConnected)
{
public void start(WebcastResponse webcastResponse, TikTokLiveClient tikTokLiveClient) {
this.tikTokLiveClient = tikTokLiveClient;
if (isConnected) {
stop();
}
if (webcastResponse.getSocketUrl().isEmpty() || webcastResponse.getSocketParamsList().isEmpty()) {
@@ -61,11 +53,19 @@ public class TikTokWebsocketClient {
}
try {
var url = getWebSocketUrl(webcastResponse);
webSocket =startWebSocket(url);
if (clientSettings.isHandleExistingMessagesOnConnect()) {
// HandleWebcastMessages(webcastResponse);
logger.info("Handling existing messages");
webResponseHandler.handle(tikTokLiveClient, webcastResponse);
}
} catch (Exception e) {
webSocketClient = startWebSocket(url);
webSocketClient.connect();
pingingTask = new TikTokWebSocketPingingTask();
pingingTask.run(webSocketClient);
isConnected = true;
} catch (Exception e)
{
isConnected =false;
throw new TikTokLiveException("Failed to connect to the websocket", e);
}
}
@@ -78,33 +78,34 @@ public class TikTokWebsocketClient {
var headers = Constants.DefaultRequestHeaders();
var clone = new TreeMap<>(clientParams);
var clone = new TreeMap<>(clientSettings.getClientParameters());
clone.putAll(headers);
clone.put(name, value);
var url = webcastResponse.getSocketUrl();
return HttpUtils.parseParametersEncode(url, clone);
}
private WebSocket startWebSocket(String url) throws Exception {
private WebSocketClient startWebSocket(String url) {
var cookie = tikTokCookieJar.parseCookies();
// System.out.println("WssIP: " + url);
// System.out.println("Cookie: " + cookie);
var map = new HashMap<String, String>();
map.put("Cookie", cookie);
return factory.openSocket()
.subprotocols("echo-protocol")
.connectTimeout(Duration.ofSeconds(15))
.header("Cookie", cookie)
.buildAsync(URI.create(url), new TikTokWebSocketListener(webResponseHandler, tikTokEventHandler)).get();
return new TikTokWebSocketListener(URI.create(url),
map,
3000,
webResponseHandler,
tikTokEventHandler,
tikTokLiveClient);
}
public void stop() {
if(isConnected && webSocket != null)
public void stop()
{
webSocket.sendClose(WebSocket.NORMAL_CLOSURE, "ok");
}
if (isConnected && webSocketClient != null) {
webSocketClient.close(1);
}
webSocketClient = null;
pingingTask = null;
isConnected = false;
}
}

View File

@@ -1,106 +1,121 @@
package io.github.jwdeveloper.tiktok.websocket;
import com.google.protobuf.ByteString;
import io.github.jwdeveloper.tiktok.TikTokLiveClient;
import io.github.jwdeveloper.tiktok.events.messages.TikTokConnectedEvent;
import io.github.jwdeveloper.tiktok.events.messages.TikTokDisconnectedEvent;
import io.github.jwdeveloper.tiktok.events.messages.TikTokErrorEvent;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveMessageParsingException;
import io.github.jwdeveloper.tiktok.exceptions.TikTokProtocolBufferException;
import io.github.jwdeveloper.tiktok.handlers.TikTokEventHandler;
import io.github.jwdeveloper.tiktok.handlers.WebResponseHandler;
import io.github.jwdeveloper.tiktok.handlers.TikTokMessageHandlerRegistration;
import io.github.jwdeveloper.tiktok.messages.WebcastResponse;
import io.github.jwdeveloper.tiktok.messages.WebcastWebsocketAck;
import io.github.jwdeveloper.tiktok.messages.WebcastWebsocketMessage;
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.drafts.Draft_6455;
import org.java_websocket.handshake.ServerHandshake;
import java.io.ByteArrayOutputStream;
import java.net.URI;
import java.net.http.WebSocket;
import java.nio.ByteBuffer;
import java.util.concurrent.CompletionStage;
import java.util.Map;
import java.util.Optional;
public class TikTokWebSocketListener extends WebSocketClient {
public class TikTokWebSocketListener implements java.net.http.WebSocket.Listener {
private final ByteArrayOutputStream accumulatedData = new ByteArrayOutputStream();
private final WebResponseHandler webResponseHandler;
private final TikTokMessageHandlerRegistration webResponseHandler;
private final TikTokEventHandler tikTokEventHandler;
private final TikTokLiveClient tikTokLiveClient;
public TikTokWebSocketListener(WebResponseHandler webResponseHandler, TikTokEventHandler tikTokEventHandler) {
public TikTokWebSocketListener(URI serverUri,
Map<String, String> httpHeaders,
int connectTimeout,
TikTokMessageHandlerRegistration webResponseHandler,
TikTokEventHandler tikTokEventHandler,
TikTokLiveClient tikTokLiveClient) {
super(serverUri, new Draft_6455(), httpHeaders,connectTimeout);
this.webResponseHandler = webResponseHandler;
this.tikTokEventHandler = tikTokEventHandler;
this.tikTokLiveClient = tikTokLiveClient;
}
@Override
public CompletionStage<?> onBinary(WebSocket webSocket, ByteBuffer data, boolean last) {
try {
var bytes = new byte[data.remaining()];
data.get(bytes);
accumulatedData.write(bytes);
if (last) {
handleBinary(webSocket, accumulatedData.toByteArray());
accumulatedData.reset();
public void onOpen(ServerHandshake serverHandshake) {
tikTokEventHandler.publish(tikTokLiveClient,new TikTokConnectedEvent());
sendPing();
}
@Override
public void onMessage(ByteBuffer bytes)
{
try {
handleBinary(bytes.array());
} catch (Exception e) {
tikTokEventHandler.publish(new TikTokErrorEvent(e));
tikTokEventHandler.publish(tikTokLiveClient, new TikTokErrorEvent(e));
}
webSocket.request(1);
return null;
sendPing();
}
@Override
public void onOpen(java.net.http.WebSocket webSocket) {
tikTokEventHandler.publish(new TikTokConnectedEvent());
webSocket.request(1);
public void onClose(int i, String s, boolean b) {
tikTokEventHandler.publish(tikTokLiveClient,new TikTokDisconnectedEvent());
}
@Override
public void onError(java.net.http.WebSocket webSocket, Throwable error) {
tikTokEventHandler.publish(new TikTokErrorEvent(error));
webSocket.request(1);
public void onError(Exception error) {
tikTokEventHandler.publish(tikTokLiveClient,new TikTokErrorEvent(error));
sendPing();
}
@Override
public CompletionStage<?> onClose(java.net.http.WebSocket webSocket, int statusCode, String reason) {
tikTokEventHandler.publish(new TikTokDisconnectedEvent());
return java.net.http.WebSocket.Listener.super.onClose(webSocket, statusCode, reason);
}
private void handleBinary(WebSocket webSocket, byte[] buffer) {
try {
var websocketMessage = WebcastWebsocketMessage.parseFrom(buffer);
if (websocketMessage.getBinary().isEmpty()) {
private void handleBinary(byte[] buffer) {
var websocketMessageOptional = getWebcastWebsocketMessage(buffer);
if (websocketMessageOptional.isEmpty()) {
return;
}
sendAckId(webSocket, websocketMessage.getId());
var websocketMessage = websocketMessageOptional.get();
sendAckId(websocketMessage.getId());
var webResponse = getWebResponseMessage(websocketMessage.getBinary());
webResponseHandler.handle(tikTokLiveClient, webResponse);
}
private Optional<WebcastWebsocketMessage> getWebcastWebsocketMessage(byte[] buffer) {
try {
var response = WebcastResponse.parseFrom(websocketMessage.getBinary());
webResponseHandler.handle(response);
} catch (Exception e) {
throw new TikTokLiveMessageParsingException("Unable to read WebcastResponse", e);
var websocketMessage = WebcastWebsocketMessage.parseFrom(buffer);
if (websocketMessage.getBinary().isEmpty()) {
return Optional.empty();
}
return Optional.of(websocketMessage);
} catch (Exception e) {
throw new TikTokLiveMessageParsingException("Unable to read WebcastWebsocketMessage", e);
throw new TikTokProtocolBufferException("Unable to parse WebcastWebsocketMessage", buffer, e);
}
}
private void pingTask(WebSocket webSocket) throws InterruptedException {
while (true) {
byte[] message = new byte[]{58, 2, 104, 98};
ByteBuffer buffer = ByteBuffer.wrap(message);
while (buffer.hasRemaining()) {
webSocket.sendPing(buffer);
}
buffer.clear();
Thread.sleep(10);
private WebcastResponse getWebResponseMessage(ByteString buffer) {
try {
return WebcastResponse.parseFrom(buffer);
} catch (Exception e) {
throw new TikTokProtocolBufferException("Unable to parse WebcastResponse", buffer.toByteArray(), e);
}
}
private void sendAckId(WebSocket webSocket, long id) {
private void sendAckId(long id) {
var serverInfo = WebcastWebsocketAck
.newBuilder()
.setType("ack")
.setId(id)
.build();
webSocket.sendBinary(serverInfo.toByteString().asReadOnlyByteBuffer(), true);
send(serverInfo.toByteString().asReadOnlyByteBuffer());
}
@Override
public void onMessage(String s) {
}
}

View File

@@ -0,0 +1,58 @@
package io.github.jwdeveloper.tiktok.websocket;
import org.java_websocket.WebSocket;
import java.util.Random;
public class TikTokWebSocketPingingTask
{
private Thread thread;
private boolean isRunning = false;
private final int MIN_TIMEOUT = 5;
private final int MAX_TIMEOUT = 100;
public void run(WebSocket webSocket)
{
var thread = new Thread(() ->
{
pingTask(webSocket);
});
isRunning =true;
thread.start();
}
public void stop()
{
if(thread != null)
{
thread.interrupt();
}
isRunning = false;
}
private void pingTask(WebSocket webSocket)
{
var random = new Random();
while (isRunning)
{
try
{
if(!webSocket.isOpen())
{
Thread.sleep(100);
continue;
}
webSocket.sendPing();
var timeout = random.nextInt(MAX_TIMEOUT)+MIN_TIMEOUT;
Thread.sleep(timeout);
}
catch (Exception e)
{
isRunning = false;
}
}
}
}

View File

@@ -0,0 +1,49 @@
package io.github.jwdeveloper.tiktok;
import com.google.protobuf.InvalidProtocolBufferException;
import io.github.jwdeveloper.tiktok.common.TikTokBaseTest;
import io.github.jwdeveloper.tiktok.messages.*;
import org.junit.Test;
public class ParseMessagesTests extends TikTokBaseTest
{
@Test
public void ShouldParseMessageWebcastLikeMessage() throws InvalidProtocolBufferException {
var bytes = getFileBytesUtf("LikeMessage.bin");
var message = WebcastLikeMessage.parseFrom(bytes);
}
@Test
public void ShouldParseMessageWebcastGiftMessage() throws InvalidProtocolBufferException {
var bytes = getFileBytesUtf("MessageWebcastGiftMessage.bin");
var message = WebcastGiftMessage.parseFrom(bytes);
}
@Test
public void ShouldParseMessageWebcastChatMessage() throws InvalidProtocolBufferException {
var bytes = getFileBytesUtf("MessageWebcastChatMessage.bin");
var message = WebcastChatMessage.parseFrom(bytes);
}
@Test
public void ShouldParseMessageWebcastImDeleteMessage() throws InvalidProtocolBufferException {
var bytes = getFileBytesUtf("MessageWebcastImDeleteMessage.bin");
var message = WebcastImDeleteMessage.parseFrom(bytes);
}
@Test
public void ShouldParseMessageWebcastSocialMessage() throws InvalidProtocolBufferException {
var bytes = getFileBytesUtf("MessageWebcastSocialMessage.bin");
var message = WebcastSocialMessage.parseFrom(bytes);
}
@Test
public void ShouldParseMessageWebcastMemberMessage() throws InvalidProtocolBufferException {
var bytes = getFileBytesUtf("WebcastMemberMessage.bin");
var message = WebcastMemberMessage.parseFrom(bytes);
}
}

View File

@@ -1,30 +0,0 @@
package io.github.jwdeveloper.tiktok;
import com.google.protobuf.InvalidProtocolBufferException;
import io.github.jwdeveloper.tiktok.messages.WebcastWebsocketMessage;
import org.junit.Test;
import java.io.IOException;
public class SerializeWebMessageTest
{
@Test
public void WebcastWebsocketMessage()
{
/*
try (var str = getClass().getClassLoader().getResourceAsStream("WebcastWebsocketMessage.bin"))
{
var bytes = str.readAllBytes();
var person = WebcastWebsocketMessage.parseFrom(bytes);
System.out.println("id: " + person.getId());
System.out.println("type: " + person.getType());
System.out.println("binary: " + person.getBinary().size());
// System.out.println("Email: " + person.getEmail());
} catch (InvalidProtocolBufferException e) {
System.out.println("Error parsing the protobuf message: " + e.getMessage());
e.printStackTrace();
} catch (IOException e) {
System.out.println("Error reading the file: " + e.getMessage());
}*/
}
}

View File

@@ -0,0 +1,26 @@
package io.github.jwdeveloper.tiktok;
import io.github.jwdeveloper.tiktok.common.TikTokBaseTest;
import io.github.jwdeveloper.tiktok.handlers.TikTokEventHandler;
import io.github.jwdeveloper.tiktok.handlers.TikTokMessageHandlerRegistration;
import org.junit.Before;
import java.util.logging.Logger;
import static org.mockito.Mockito.mock;
public class WebResponseHandlerTests extends TikTokBaseTest {
public static TikTokMessageHandlerRegistration sut;
@Before
public void before() {
var mockEventHandler = mock(TikTokEventHandler.class);
var mockGiftManager = mock(TikTokGiftManager.class);
var mockRoomInfo = mock(TikTokRoomInfo.class);
var mockClientSettings = mock(ClientSettings.class);
var mockLogger = mock(Logger.class);
sut = new TikTokMessageHandlerRegistration(mockEventHandler,mockClientSettings,mockLogger, mockGiftManager, mockRoomInfo);
}
}

View File

@@ -0,0 +1,34 @@
package io.github.jwdeveloper.tiktok.common;
import com.google.protobuf.InvalidProtocolBufferException;
import io.github.jwdeveloper.tiktok.messages.WebcastWebsocketMessage;
import java.io.IOException;
import java.util.Base64;
public class TikTokBaseTest
{
public byte[] getFileBytes(String path)
{
try {
var stream = getClass().getClassLoader().getResourceAsStream(path);
var bytes= stream.readAllBytes();
stream.close();
return bytes;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public byte[] getFileBytesUtf(String path)
{
try {
var stream = getClass().getClassLoader().getResourceAsStream(path);
var bytes= stream.readAllBytes();
stream.close();
return Base64.getDecoder().decode(bytes);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
CjUKFldlYmNhc3RJbURlbGV0ZU1lc3NhZ2UQhZab+vyKvvJkGJKWhYz+o7DxZCC0sajzoTEwARoJhojH2ojIjaBk

View File

@@ -0,0 +1 @@
CskHMAFChgcaDgoJI2ZmZmZmZmZmIJADIp8GCAsSDgoJI2ZmZmZmZmZmIJADqgGJBgqGBgiFiNKIpZnf0WIaCm1yIEEuTC5GLkFK/wQKxgFodHRwczovL3AxNi1zaWduLXVzZWFzdDJhLnRpa3Rva2Nkbi5jb20vdG9zLXVzZWFzdDJhLWF2dC0wMDY4LWV1dHRwLzkxODE3OWQ4MzQ0YWZiYjk3OTAzYjYyMGQwOTg1ZGEzfnRwbHYtdGlrdG9rLXNocmluazo3Mjo3Mi53ZWJwP3gtZXhwaXJlcz0xNjkyOTAwMDAwJngtc2lnbmF0dXJlPUE0RmtOWDMlMkIxNiUyRjlxeENvR1JReEczNFRnRUUlM0QKtAFodHRwczovL3AxNi1zaWduLXVzZWFzdDJhLnRpa3Rva2Nkbi5jb20vdG9zLXVzZWFzdDJhLWF2dC0wMDY4LWV1dHRwLzkxODE3OWQ4MzQ0YWZiYjk3OTAzYjYyMGQwOTg1ZGEzfmM1XzEwMHgxMDAud2VicD94LWV4cGlyZXM9MTY5MjkwMDAwMCZ4LXNpZ25hdHVyZT1EOHlYQUhNVGxGeUR5N3dqT3BXRllrUlBBWnclM0QKtgFodHRwczovL3AxNi1zaWduLXVzZWFzdDJhLnRpa3Rva2Nkbi5jb20vdG9zLXVzZWFzdDJhLWF2dC0wMDY4LWV1dHRwLzkxODE3OWQ4MzQ0YWZiYjk3OTAzYjYyMGQwOTg1ZGEzfmM1XzEwMHgxMDAuanBlZz94LWV4cGlyZXM9MTY5MjkwMDAwMCZ4LXNpZ25hdHVyZT1IYkIwNk00N2Y0JTJGT29jUWxla0gwREdDdkY3MCUzRBJEMTAweDEwMC90b3MtdXNlYXN0MmEtYXZ0LTAwNjgtZXV0dHAvOTE4MTc5ZDgzNDRhZmJiOTc5MDNiNjIwZDA5ODVkYTOyAQgI9h8Qmw0YAboBAIICALICCy56YXBpc3l3YW5l8gJMTVM0d0xqQUJBQUFBYXdVMzNGbnBIYllkMHdra1djeC1DTnBzeDdXT0lqOXdtOTBwQ2tRUmZPekJuZVdZczdXT0plSm5GVU5kdFh0MwoecG1fbXRfZ3VpZGFuY2Vfdmlld2VyXzEwX3NoYXJlEjJ7MDp1c2VyfSBzaGFyZWQgdGhlIExJVkUgd2l0aCBtb3JlIHRoYW4gMTAgZnJpZW5kc1ACsAEFwAECChRXZWJjYXN0U29jaWFsTWVzc2FnZRCglsC+2OG+8mQYoJaZxvrevPJkIOTf0vOhMUgBuAECEoYGSv8ECsYBaHR0cHM6Ly9wMTYtc2lnbi11c2Vhc3QyYS50aWt0b2tjZG4uY29tL3Rvcy11c2Vhc3QyYS1hdnQtMDA2OC1ldXR0cC85MTgxNzlkODM0NGFmYmI5NzkwM2I2MjBkMDk4NWRhM350cGx2LXRpa3Rvay1zaHJpbms6NzI6NzIud2VicD94LWV4cGlyZXM9MTY5MjkwMDAwMCZ4LXNpZ25hdHVyZT1BNEZrTlgzJTJCMTYlMkY5cXhDb0dSUXhHMzRUZ0VFJTNECrQBaHR0cHM6Ly9wMTYtc2lnbi11c2Vhc3QyYS50aWt0b2tjZG4uY29tL3Rvcy11c2Vhc3QyYS1hdnQtMDA2OC1ldXR0cC85MTgxNzlkODM0NGFmYmI5NzkwM2I2MjBkMDk4NWRhM35jNV8xMDB4MTAwLndlYnA/eC1leHBpcmVzPTE2OTI5MDAwMDAmeC1zaWduYXR1cmU9RDh5WEFITVRsRnlEeTd3ak9wV0ZZa1JQQVp3JTNECrYBaHR0cHM6Ly9wMTYtc2lnbi11c2Vhc3QyYS50aWt0b2tjZG4uY29tL3Rvcy11c2Vhc3QyYS1hdnQtMDA2OC1ldXR0cC85MTgxNzlkODM0NGFmYmI5NzkwM2I2MjBkMDk4NWRhM35jNV8xMDB4MTAwLmpwZWc/eC1leHBpcmVzPTE2OTI5MDAwMDAmeC1zaWduYXR1cmU9SGJCMDZNNDdmNCUyRk9vY1FsZWtIMERHQ3ZGNzAlM0QSRDEwMHgxMDAvdG9zLXVzZWFzdDJhLWF2dC0wMDY4LWV1dHRwLzkxODE3OWQ4MzQ0YWZiYjk3OTAzYjYyMGQwOTg1ZGEzsgEIEJsNGAEI9h+6AQCCAgCyAgsuemFwaXN5d2FuZfICTE1TNHdMakFCQUFBQWF3VTMzRm5wSGJZZDB3a2tXY3gtQ05wc3g3V09Jajl3bTkwcENrUVJmT3pCbmVXWXM3V09KZUpuRlVOZHRYdDMIhYjSiKWZ39FiGgptciBBLkwuRi5BGAEgAyoCLTFAGQ==

File diff suppressed because one or more lines are too long

View File

@@ -1,32 +0,0 @@
 ŔĆł»„ÖF¸E *
compress_typenone2pb:msgBš
Ű
WebcastLikeMessageş

WebcastLikeMessageˇ¤ŚöÍííd żÚřęëíd0
pm_mt_msg_viewer{0:user} liked the LIVE
#ffffffff "
Ş}
{ ˆ•ÉÔëdhulajnoga czek˛hulajnoga.czek5ňLMS4wLjABAAAAx4-lIrRjH0pMT0D3laROVsnU-4u6P1tN4td82AQTCHjKZ-CHaF_DluNSFQQp1s4DHP°¸Ŕ ĆĐŇź1†q*Ü ˆ•ÉÔëdhulajnoga czek˛ş ˛hulajnoga.czek5J˙
Čhttps://p16-sign-useast2a.tiktokcdn.com/tos-useast2a-avt-0068-euttp/26aa68e29831ff0c9d4fb37875efffdd~tplv-tiktok-shrink:72:72.webp?x-expires=1692291600&x-signature=Z2ZM2T%2FckIxKSj%2BJY%2BONoypyMwo%3D
´https://p16-sign-useast2a.tiktokcdn.com/tos-useast2a-avt-0068-euttp/26aa68e29831ff0c9d4fb37875efffdd~c5_100x100.webp?x-expires=1692291600&x-signature=gJgrVHIZWnVCif4Z6gXuhnl1pn8%3D
´https://p16-sign-useast2a.tiktokcdn.com/tos-useast2a-avt-0068-euttp/26aa68e29831ff0c9d4fb37875efffdd~c5_100x100.jpeg?x-expires=1692291600&x-signature=ITqTLj6v7UVl12HeeGa6a6FYNpc%3DD100x100/tos-useast2a-avt-0068-euttp/26aa68e29831ff0c9d4fb37875efffddŞ0 ňLMS4wLjABAAAAx4-lIrRjH0pMT0D3laROVsnU-4u6P1tN4td82AQTCHjKZ-CHaF_DluNSFQQp1s4DîR„sslocal://webcast_webview_popup?gravity=bottom&show_mask=1&url=https%3A%2F%2Flf16-web.tiktokcdn.com%2Fobj%2Fies-hotsoon-draft-sg%2Ftiktok-live-faq%2Ftiktok_live_revenue_new_gifter_details.html&web_bg_color=FFFFFF&height=60%25&mask_bg_color=000000b3&use_spark=1b:"10000037267585271263611681*0
27154120369731914523 pm_mt_live_ng_im
New gifter( Ü sslocal://webcast_lynxview_popup?use_spark=1&url=https%3A%2F%2Flf16-gecko-source.tiktokcdn.com%2Fobj%2Fbyte-gurd-source-sg%2Ftiktok%2Ffe%2Flive%2Ftiktok_live_revenue_user_level_main%2Fsrc%2Fpages%2Fprivilege%2Fpanel%2Ftemplate.js&hide_status_bar=0&hide_nav_bar=1&container_bg_color=00000000&height=90%25&bdhm_bid=tiktok_live_revenue_user_level_main&use_forest=1XbN
271383811767874578280".mock_fix_width_transparent_7138381176787457828*Ő+webcast-va/grade_badge_icon_lite_lv1_v1.png:ésslocal://webcast_lynxview_popup?use_spark=1&url=https%3A%2F%2Flf16-gecko-source.tiktokcdn.com%2Fobj%2Fbyte-gurd-source-sg%2Ftiktok%2Ffe%2Flive%2Ftiktok_live_revenue_user_level_main%2Fsrc%2Fpages%2Fprivilege%2Fpanel%2Ftemplate.js&hide_status_bar=0&hide_nav_bar=1&container_bg_color=00000000&height=90%25&bdhm_bid=tiktok_live_revenue_user_level_main&use_forest=1
\https://p16-webcast.tiktokcdn.com/webcast-va/grade_badge_icon_lite_lv1_v1.png~tplv-obj.image
\https://p19-webcast.tiktokcdn.com/webcast-va/grade_badge_icon_lite_lv1_v1.png~tplv-obj.image"2* b
 #99789EE7x2 : " Z
 #99789EE7€÷( R„sslocal://webcast_webview_popup?gravity=bottom&show_mask=1&url=https%3A%2F%2Flf16-web.tiktokcdn.com%2Fobj%2Fies-hotsoon-draft-sg%2Ftiktok-live-faq%2Ftiktok_live_revenue_new_gifter_details.html&web_bg_color=FFFFFF&height=60%25&mask_bg_color=000000b3&use_spark=1Xb:
271541203697319145231"10000037267585271263611681*¦x ˆŰ 0:„sslocal://webcast_webview_popup?gravity=bottom&show_mask=1&url=https%3A%2F%2Flf16-web.tiktokcdn.com%2Fobj%2Fies-hotsoon-draft-sg%2Ftiktok-live-faq%2Ftiktok_live_revenue_new_gifter_details.html&web_bg_color=FFFFFF&height=60%25&mask_bg_color=000000b3&use_spark=1
Shttps://p16-webcast.tiktokcdn.com/webcast-va/new_gifter_badge_v3.png~tplv-obj.image
Shttps://p19-webcast.tiktokcdn.com/webcast-va/new_gifter_badge_v3.png~tplv-obj.image"webcast-va/new_gifter_badge_v3.png
pm_mt_live_ng_im
New gifterZ
 #803F3F3Fb
 #803F3F3FˆË”ʍÁcŔ
pm_mt_msg_viewer{0:user} liked the LIVE
#ffffffff "
Ş}
{˛hulajnoga.czek5ňLMS4wLjABAAAAx4-lIrRjH0pMT0D3laROVsnU-4u6P1tN4td82AQTCHjKZ-CHaF_DluNSFQQp1s4D ˆ•ÉÔëdhulajnoga czekHˇ¤ŚöÍííd)1692120689751_7267603020139989890_1_1_0_0č ×ŘŇź1*-@H

View File

@@ -1,9 +1,13 @@
# TikTok-Live-Java
A Java library based on [TikTok-Connector](https://github.com/isaackogan/TikTok-Live-Connector) and [TikTokLiveSharp](https://github.com/sebheron/TikTokLiveSharp). Use it to receive live stream events such as comments and gifts in realtime from [TikTok LIVE](https://www.tiktok.com/live) by connecting to TikTok's internal WebCast push service. The package includes a wrapper that connects to the WebCast service using just the username (`uniqueId`). This allows you to connect to your own live chat as well as the live chat of other streamers. No credentials are required. Besides [Chat Comments](#chat), other events such as [Members Joining](#member), [Gifts](#gift), [Subscriptions](#subscribe), [Viewers](#roomuser), [Follows](#social), [Shares](#social), [Questions](#questionnew), [Likes](#like) and [Battles](#linkmicbattle) can be tracked. You can also send [automatic messages](#send-chat-messages) into the chat by providing your Session ID.
[![](https://jitpack.io/v/jwdeveloper/TikTok-Live-Java.svg)](https://jitpack.io/#jwdeveloper/TikTok-Live-Java)
# TikTokLive Java
A Java library based on [TikTokLive](https://github.com/isaackogan/TikTokLive) and [TikTokLiveSharp](https://github.com/sebheron/TikTokLiveSharp). Use it to receive live stream events such as comments and gifts in realtime from [TikTok LIVE](https://www.tiktok.com/live) by connecting to TikTok's internal WebCast push service. The package includes a wrapper that connects to the WebCast service using just the username (`uniqueId`). This allows you to connect to your own live chat as well as the live chat of other streamers. No credentials are required. Besides [Chat Comments](#chat), other events such as [Members Joining](#member), [Gifts](#gift), [Subscriptions](#subscribe), [Viewers](#roomuser), [Follows](#social), [Shares](#social), [Questions](#questionnew), [Likes](#like) and [Battles](#linkmicbattle) can be tracked. You can also send [automatic messages](#send-chat-messages) into the chat by providing your Session ID.
Join the support [discord](https://discord.gg/e2XwPNTBBr) and visit the `#java-support` channel for questions, contributions and ideas. Feel free to make pull requests with missing/new features, fixes, etc
Do you prefer other programming languages?
- **Node** orginal: [TikTok-Live-Connector](https://github.com/isaackogan/TikTok-Live-Connector) by [@isaackogan](https://github.com/isaackogan)
- **Node** orginal: [TikTok-Live-Connector](https://github.com/isaackogan/TikTok-Live-Connector) by [@zerodytrash](https://github.com/zerodytrash)
- **Python** rewrite: [TikTokLive](https://github.com/isaackogan/TikTokLive) by [@isaackogan](https://github.com/isaackogan)
- **Go** rewrite: [GoTikTokLive](https://github.com/Davincible/gotiktoklive) by [@Davincible](https://github.com/Davincible)
- **C#** rewrite: [TikTokLiveSharp](https://github.com/frankvHoof93/TikTokLiveSharp) by [@frankvHoof93](https://github.com/frankvHoof93)
@@ -12,15 +16,14 @@ Do you prefer other programming languages?
#### Overview
- [Getting started](#getting-started)
- [Params and options](#params-and-options)
- [Configuration](#configuration)
- [Methods](#methods)
- [Events](#events)
- [Examples](#examples)
- [Contributing](#contributing)
## Getting started
1. Install the package via Maven (dependecies will be simplify in future)
1. Install the package via Maven
```xml
<repositories>
@@ -29,32 +32,20 @@ Do you prefer other programming languages?
<url>https://jitpack.io</url>
</repository>
</repositories>
```
```xml
<dependencies>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>3.24.1</version>
<groupId>com.github.jwdeveloper.TikTok-Live-Java</groupId>
<artifactId>Client</artifactId>
<version>0.0.14-Release</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.10.1</version>
</dependency>
<dependency>
<groupId>com.github.jwdeveloper.TikTok-Live-Java</groupId>
<artifactId>API</artifactId>
<version>0.0.4-Release</version>
</dependency>
<dependency>
<groupId>com.github.jwdeveloper.TikTok-Live-Java</groupId>
<artifactId>Client</artifactId>
<version>0.0.4-Release</version>
</dependency>
</dependencies>
```
2. Create your first chat connection
@@ -63,36 +54,69 @@ Do you prefer other programming languages?
public static void main(String[] args)
{
// Username of someone who is currently live
var tiktokUsername = "officialgeilegisela";
var tiktokUsername = "jwdevtiktok";
TikTokLive.newClient(tiktokUsername)
.onConnected(event ->
.onConnected((client, event) ->
{
System.out.println("Connected");
})
.onJoin(event ->
.onJoin((client, event) ->
{
System.out.println("User joined -> " + event.getUser().getNickName());
})
.onComment(event ->
.onComment((client, event) ->
{
System.out.println(event.getUser().getUniqueId() + ": " + event.getText());
})
.onError(event ->
.onEvent((client, event) ->
{
System.out.println("Viewers count: "+client.getRoomInfo().getViewersCount());
})
.onError((client, event) ->
{
event.getException().printStackTrace();
})
.buildAndRun();
System.in.read();
}
```
## Configuration
```java
public class ConfigurationExample
{
public static void main(String[] args) throws IOException {
var tiktokUsername = "jwdevtiktok";
TikTokLive.newClient(tiktokUsername)
.configure(clientSettings ->
{
clientSettings.setHostName(Main.TEST_TIKTOK_USER); // TikTok user name
clientSettings.setClientLanguage("en"); // Language
clientSettings.setTimeout(Duration.ofSeconds(2)); // Connection timeout
clientSettings.setLogLevel(Level.ALL); // Log level
clientSettings.setDownloadGiftInfo(true); // Downloading meta information about gifts. You can access it by client.getGiftManager().getGiftsInfo();
clientSettings.setPrintMessageData(true); // Printing TikTok Protocol buffer messages in Base64 format
clientSettings.setPrintToConsole(true); // Printing all logs to console even if log level is Level.OFF
clientSettings.setHandleExistingMessagesOnConnect(true); // Invokes all TikTok events that had occurred before connection
clientSettings.setRetryOnConnectionFailure(true); // Reconnecting if TikTok user is offline
clientSettings.setRetryConnectionTimeout(Duration.ofSeconds(1)); // Timeout before next reconnection
})
.buildAndRun();
System.in.read();
}
}
```
## Methods
A `TikTokLive` object contains the following methods.
A `client (LiveClient)` object contains the following methods.
| Method Name | Description |
| ----------- | ----------- |
| connect | Connects to the live stream chat.<br>Returns a `Promise` which will be resolved when the connection is successfully established. |
| connect | Connects to the live stream. |
| disconnect | Disconnects the connection. |
| getGiftManager | Gets the meta informations about all gifts. |
| getRoomInfo | Gets the current room info from TikTok API including streamer info, room status and statistics. |
## Events

View File

@@ -2,11 +2,48 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<!-- READ ME
This POM file is configured to run only inside TikTokLiveJava project
In case you have copied it follows this stets to fix console errors
1. Remember to remove
<parent>
<artifactId>TikTokLiveJava</artifactId>
<groupId>io.github.jwdeveloper.tiktok</groupId>
<version>1.0.0</version>
<version>0.0.13-Release</version>
</parent>
2. Paste
<repositories>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>com.github.jwdeveloper.TikTok-Live-Java</groupId>
<artifactId>Client</artifactId>
<version>0.0.14-Release</version> <- Change with latest version of TikTokJava
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.10.1</version>
</dependency>
</dependencies>
I hope it will help you :)
-->
<parent>
<artifactId>TikTokLiveJava</artifactId>
<groupId>io.github.jwdeveloper.tiktok</groupId>
<version>0.0.14-Release</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>TestApplication</artifactId>

View File

@@ -0,0 +1,27 @@
package io.github.jwdeveloper.tiktok;
import java.io.IOException;
import java.time.Duration;
import java.util.logging.Level;
public class ConfigurationExample {
public static void main(String[] args) throws IOException {
TikTokLive.newClient(Main.TEST_TIKTOK_USER)
.configure(clientSettings ->
{
clientSettings.setHostName(Main.TEST_TIKTOK_USER); // TikTok user name
clientSettings.setClientLanguage("en"); // Language
clientSettings.setTimeout(Duration.ofSeconds(2)); // Connection timeout
clientSettings.setLogLevel(Level.ALL); // Log level
clientSettings.setDownloadGiftInfo(true); // Downloading meta information about gifts. You can access it by client.getGiftManager().getGiftsInfo();
clientSettings.setPrintMessageData(true); // Printing TikTok Protocol buffer messages in Base64 format
clientSettings.setPrintToConsole(true); // Printing all logs to console even if log level is Level.OFF
clientSettings.setHandleExistingMessagesOnConnect(true); // Invokes all TikTok events that had occurred before connection
clientSettings.setRetryOnConnectionFailure(true); // Reconnecting if TikTok user is offline
clientSettings.setRetryConnectionTimeout(Duration.ofSeconds(1)); // Timeout before next reconnection
})
.buildAndRun();
System.in.read();
}
}

View File

@@ -1,15 +1,22 @@
package io.github.jwdeveloper.tiktok;
import io.github.jwdeveloper.tiktok.events.messages.*;
import io.github.jwdeveloper.tiktok.live.LiveClient;
import java.io.IOException;
import java.time.Duration;
public class Main {
public static String TEST_USER_SUBJECT = "stiflerhub";
public static String TEST_TIKTOK_USER = "olchik.m1";
public static void main(String[] args) throws IOException {
var client = TikTokLive.newClient(TEST_USER_SUBJECT)
var client = TikTokLive.newClient(TEST_TIKTOK_USER)
.configure(clientSettings ->
{
clientSettings.setRetryConnectionTimeout(Duration.ofSeconds(5));
clientSettings.setRetryOnConnectionFailure(true);
})
.onConnected(Main::onConnected)
.onDisconnected(Main::onDisconnected)
.onRoomViewerData(Main::onViewerData)
@@ -21,60 +28,63 @@ public class Main {
.onLike(Main::onLike)
.onGiftMessage(Main::onGiftMessage)
.onEmote(Main::onEmote)
.onError(tikTokErrorEvent ->
.onError((_client, error) ->
{
// tikTokErrorEvent.getException().printStackTrace();
error.getException().printStackTrace();
})
.onEvent((liveClient, event) ->
{
var viewers = liveClient.getRoomInfo().getViewersCount();
})
.buildAndRun();
var viewers = client.getRoomInfo().getViewersCount();
System.in.read();
}
private static void onConnected(TikTokConnectedEvent e) {
private static void onConnected(LiveClient tikTokLive, TikTokConnectedEvent e) {
print("Connected");
}
private static void onDisconnected(TikTokDisconnectedEvent e) {
private static void onDisconnected(LiveClient tikTokLive, TikTokDisconnectedEvent e) {
print("Disconnected");
}
private static void onViewerData(TikTokRoomViewerDataEvent e) {
private static void onViewerData(LiveClient tikTokLive, TikTokRoomViewerDataEvent e) {
print("Viewer count is:", e.getViewerCount());
}
private static void onJoin(TikTokJoinEvent e) {
private static void onJoin(LiveClient tikTokLive, TikTokJoinEvent e) {
print(e.getUser().getUniqueId(), "joined!");
}
private static void onComment(TikTokCommentEvent e) {
print("DUPA: "+e.getText());
// print(e.getUser().getUniqueId(), e.getText());
private static void onComment(LiveClient tikTokLive, TikTokCommentEvent e) {
print(e.getUser().getUniqueId(), e.getText());
}
private static void onFollow(TikTokFollowEvent e) {
private static void onFollow(LiveClient tikTokLive, TikTokFollowEvent e) {
print(e.getNewFollower().getUniqueId(), "followed!");
}
private static void onShare(TikTokShareEvent e) {
private static void onShare(LiveClient tikTokLive, TikTokShareEvent e) {
print(e.getUser().getUniqueId(), "shared!");
}
private static void onSubscribe(TikTokSubscribeEvent e) {
private static void onSubscribe(LiveClient tikTokLive, TikTokSubscribeEvent e) {
print(e.getNewSubscriber().getUniqueId(), "subscribed!");
}
private static void onLike(TikTokLikeEvent e) {
private static void onLike(LiveClient tikTokLive, TikTokLikeEvent e) {
print(e.getSender().getUniqueId(), "liked!");
}
private static void onGiftMessage(TikTokGiftMessageEvent e) {
private static void onGiftMessage(LiveClient tikTokLive, TikTokGiftMessageEvent e)
{
print(e.getSender().getUniqueId(), "sent", e.getAmount(), "x", e.getGift().getName());
}
private static void onEmote(TikTokEmoteEvent e) {
private static void onEmote(LiveClient tikTokLive, TikTokEmoteEvent e) {
print(e.getUser().getUniqueId(), "sent", e.getEmoteId());
}
@@ -83,6 +93,6 @@ public class Main {
for (var message : messages) {
sb.append(message).append(" ");
}
System.out.println(sb.toString());
System.out.println(sb);
}
}

View File

@@ -1,30 +1,37 @@
package io.github.jwdeveloper.tiktok;
public class SimpleExample {
public static void main(String[] args) {
// Username of someone who is currently live
var tiktokUsername = "officialgeilegisela";
import java.io.IOException;
TikTokLive.newClient(tiktokUsername)
.clientSettings(settings ->
public class SimpleExample {
public static void main(String[] args) throws IOException {
TikTokLive.newClient(Main.TEST_TIKTOK_USER)
.onFollow((liveClient, event) ->
{
System.out.println("Follow joined -> " + event.getNewFollower().getNickName());
})
.onConnected(event ->
.onConnected((client, event) ->
{
System.out.println("Connected");
})
.onJoin(event ->
.onJoin((client, event) ->
{
System.out.println("User joined -> " + event.getUser().getNickName());
})
.onComment(event ->
.onComment((client, event) ->
{
System.out.println(event.getUser().getUniqueId() + ": " + event.getText());
})
.onError(event ->
.onEvent((client, event) ->
{
System.out.println("Viewers count: "+client.getRoomInfo().getViewersCount());
})
.onError((client, event) ->
{
event.getException().printStackTrace();
})
.buildAndRun();
System.in.read();
}
}

View File

@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>TikTokLiveJava</artifactId>
<groupId>io.github.jwdeveloper.tiktok</groupId>
<version>0.0.14-Release</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>Tools-EventsCollector</artifactId>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.xerial</groupId>
<artifactId>sqlite-jdbc</artifactId>
<version>3.34.0</version> <!-- Use the latest version available -->
</dependency>
<dependency>
<groupId>org.jdbi</groupId>
<artifactId>jdbi3-core</artifactId>
<version>3.23.0</version>
</dependency>
<dependency>
<groupId>org.jdbi</groupId>
<artifactId>jdbi3-sqlobject</artifactId>
<version>3.23.0</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.32</version> <!-- Use the latest version available -->
</dependency>
<dependency>
<groupId>io.github.jwdeveloper.tiktok</groupId>
<artifactId>Client</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,99 @@
package io.github.jwdeveloper.tiktok.tools.collector;
import io.github.jwdeveloper.tiktok.TikTokLive;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveMessageException;
import io.github.jwdeveloper.tiktok.tools.collector.db.TikTokDatabase;
import io.github.jwdeveloper.tiktok.tools.collector.tables.ExceptionInfoModel;
import io.github.jwdeveloper.tiktok.tools.collector.tables.TikTokErrorModel;
import io.github.jwdeveloper.tiktok.tools.collector.tables.TikTokMessageModel;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
public class Main {
//https://protobuf-decoder.netlify.app/
/*
mia_tattoo
moniczkka
besin1276
*/
public static List<String> ignoredEvents;
public static void main(String[] args) throws SQLException {
ignoredEvents = List.of("TikTokJoinEvent","TikTokLikeEvent");
var db = new TikTokDatabase("test");
db.init();
var users = new ArrayList<String>();
users.add("mia_tattoo");
users.add("moniczkka");
users.add("besin1276");
for(var user : users)
{
runTikTokLiveInstance(user, db);
}
}
private static void runTikTokLiveInstance(String tiktokUser, TikTokDatabase tikTokDatabase)
{
TikTokLive.newClient(tiktokUser)
.onWebsocketMessage((liveClient, event) ->
{
var eventName = event.getEvent().getClass().getSimpleName();
if(ignoredEvents.contains(eventName))
{
return;
}
var binary = Base64.getEncoder().encodeToString(event.getMessage().getBinary().toByteArray());
var model = TikTokMessageModel.builder()
.type("message")
.hostName(tiktokUser)
.eventName(eventName)
.eventContent(binary)
.build();
tikTokDatabase.insertMessage(model);
System.out.println("EVENT: ["+tiktokUser+"] " + eventName);
})
.onError((liveClient, event) ->
{
var exception = event.getException();
var exceptionContent = ExceptionInfoModel.getStackTraceAsString(exception);
var builder = TikTokErrorModel.builder();
if (exception instanceof TikTokLiveMessageException ex) {
builder.hostName(tiktokUser)
.errorName(ex.messageName())
.errorType("error-message")
.exceptionContent(exceptionContent)
.message(ex.messageToBase64())
.response(ex.webcastResponseToBase64());
} else {
builder.hostName(tiktokUser)
.errorName(exception.getClass().getSimpleName())
.errorType("error-system")
.exceptionContent(exceptionContent)
.message("")
.response("");
}
var error = builder.build();
tikTokDatabase.insertError(error);
System.out.println("ERROR: "+error.getErrorName());
exception.printStackTrace();
})
.buildAndRun();
}
}

View File

@@ -0,0 +1,29 @@
package io.github.jwdeveloper.tiktok.tools.collector.db;
public class SqlConsts
{
public static String CREATE_MESSAGES_TABLE = """
CREATE TABLE IF NOT EXISTS TikTokMessageModel (
id INTEGER PRIMARY KEY AUTOINCREMENT,
hostName TEXT,
type TEXT,
eventName TEXT,
eventContent TEXT,
createdAt TEXT
);
""";
public static String CREATE_ERROR_TABLE = """
CREATE TABLE IF NOT EXISTS TikTokErrorModel (
id INT AUTO_INCREMENT PRIMARY KEY,
hostName VARCHAR(255),
errorName VARCHAR(255),
errorType VARCHAR(255),
exceptionContent TEXT,
message TEXT,
response TEXT,
createdAt DATETIME
);
""";
}

View File

@@ -0,0 +1,50 @@
package io.github.jwdeveloper.tiktok.tools.collector.db;
import io.github.jwdeveloper.tiktok.tools.collector.tables.TikTokErrorModel;
import io.github.jwdeveloper.tiktok.tools.collector.tables.TikTokMessageModel;
import org.jdbi.v3.core.Jdbi;
import org.jdbi.v3.sqlobject.SqlObjectPlugin;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class TikTokDatabase
{
private final String database;
private TikTokMessageModelDAO messagesTable;
private TikTokErrorModelDAO errorTable;
public TikTokDatabase(String database) {
this.database =database;
}
public void init() throws SQLException {
var jdbcUrl ="jdbc:sqlite:"+database+".db";
var connection = DriverManager.getConnection(jdbcUrl);
Jdbi jdbi = Jdbi.create(jdbcUrl)
.installPlugin(new SqlObjectPlugin());
jdbi.useHandle(handle -> {
handle.execute(SqlConsts.CREATE_MESSAGES_TABLE);
handle.execute(SqlConsts.CREATE_ERROR_TABLE);
});
messagesTable = jdbi.onDemand(TikTokMessageModelDAO.class);
errorTable = jdbi.onDemand(TikTokErrorModelDAO.class);
}
public void insertMessage(TikTokMessageModel message)
{
var dateFormat = new SimpleDateFormat("dd:MM:yyyy HH:mm:ss.SSS");
message.setCreatedAt(dateFormat.format(new Date()));
messagesTable.insertTikTokMessage(message);
}
public void insertError(TikTokErrorModel message)
{
var dateFormat = new SimpleDateFormat("dd:MM:yyyy HH:mm:ss.SSS");
message.setCreatedAt(dateFormat.format(new Date()));
errorTable.insertTikTokMessage(message);
}
}

View File

@@ -0,0 +1,13 @@
package io.github.jwdeveloper.tiktok.tools.collector.db;
import io.github.jwdeveloper.tiktok.tools.collector.tables.TikTokErrorModel;
import org.jdbi.v3.sqlobject.customizer.BindBean;
import org.jdbi.v3.sqlobject.statement.SqlUpdate;
public interface TikTokErrorModelDAO
{
@SqlUpdate("INSERT INTO TikTokErrorModel (hostName, errorName, errorType, exceptionContent, message, response, createdAt) " +
"VALUES (:hostName, :errorName, :errorType, :exceptionContent, :message, :response, :createdAt)")
void insertTikTokMessage(@BindBean TikTokErrorModel message);
}

View File

@@ -0,0 +1,12 @@
package io.github.jwdeveloper.tiktok.tools.collector.db;
import io.github.jwdeveloper.tiktok.tools.collector.tables.TikTokMessageModel;
import org.jdbi.v3.sqlobject.customizer.BindBean;
import org.jdbi.v3.sqlobject.statement.SqlUpdate;
public interface TikTokMessageModelDAO
{
@SqlUpdate("INSERT INTO TikTokMessageModel (hostName, eventName,type, eventContent, createdAt) " +
"VALUES (:hostName, :eventName, :type, :eventContent, :createdAt)")
void insertTikTokMessage(@BindBean TikTokMessageModel message);
}

View File

@@ -0,0 +1,31 @@
package io.github.jwdeveloper.tiktok.tools.collector.tables;
import java.io.PrintWriter;
import java.io.StringWriter;
public class ExceptionInfoModel
{
private String message;
private String stackTrace;
public ExceptionInfoModel(Throwable throwable) {
this.message = throwable.getMessage();
this.stackTrace = getStackTraceAsString(throwable);
}
public static String getStackTraceAsString(Throwable throwable) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
throwable.printStackTrace(pw);
return sw.toString();
}
// Getters for message and stackTrace
public String getMessage() {
return message;
}
public String getStackTrace() {
return stackTrace;
}
}

View File

@@ -0,0 +1,25 @@
package io.github.jwdeveloper.tiktok.tools.collector.tables;
import lombok.Builder;
import lombok.Data;
@Data
@Builder
public class TikTokErrorModel
{
private Integer id;
private String hostName;
private String errorName;
private String errorType;
private String exceptionContent;
private String message;
private String response;
private String createdAt;
}

View File

@@ -0,0 +1,24 @@
package io.github.jwdeveloper.tiktok.tools.collector.tables;
import lombok.Builder;
import lombok.Data;
import java.util.Date;
@Data
@Builder
public class TikTokMessageModel
{
private Integer id;
private String hostName;
private String eventName;
private String type;
private String eventContent;
private String createdAt;
}

View File

@@ -5,7 +5,7 @@
<parent>
<artifactId>TikTokLiveJava</artifactId>
<groupId>io.github.jwdeveloper.tiktok</groupId>
<version>1.0.0</version>
<version>0.0.14-Release</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -33,6 +33,11 @@
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.13.1</version> <!-- Check for the latest version -->
</dependency>
<dependency>
<groupId>io.github.jwdeveloper.tiktok</groupId>
<artifactId>Client</artifactId>

View File

@@ -1,12 +1,9 @@
package io.github.jwdeveloper.tiktok.events_generator;
import io.github.jwdeveloper.tiktok.FilesUtility;
import io.github.jwdeveloper.tiktok.utils.FilesUtility;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.List;
public class EventsGenerator
{

View File

@@ -29,7 +29,7 @@ public class EventsInterfaceGenerator {
public String generateInterface(String packageName, Set<Class<? extends TikTokEvent>> eventsClasses) {
TypeSpec.Builder classBuilder = TypeSpec.interfaceBuilder("TikTokEvents");
TypeSpec.Builder classBuilder = TypeSpec.interfaceBuilder("TikTokEventBuilder");
classBuilder.addModifiers(Modifier.PUBLIC);
classBuilder.addTypeVariable(TypeVariableName.get("T"));
@@ -46,7 +46,7 @@ public class EventsInterfaceGenerator {
MethodSpec.Builder constructorBuilder = MethodSpec.methodBuilder("on" + methodName);
var name = "Consumer<" + clazzName + ">";
var name = "TikTokEventConsumer<" + clazzName + ">";
constructorBuilder.addModifiers(Modifier.ABSTRACT, Modifier.PUBLIC);
constructorBuilder.addParameter(ClassName.bestGuess(name), "event");
constructorBuilder.returns(TypeVariableName.get("T"));
@@ -91,7 +91,7 @@ public class EventsInterfaceGenerator {
MethodSpec.Builder constructorBuilder = MethodSpec.methodBuilder( methodName);
var name = "Consumer<" + clazzName + ">";
var name = "TikTokEventConsumer<" + clazzName + ">";
constructorBuilder.addModifiers( Modifier.PUBLIC);
constructorBuilder.addParameter(ClassName.bestGuess(name), "event");
constructorBuilder.addStatement("tikTokEventHandler.subscribe("+clazzName+".class,event)");

View File

@@ -0,0 +1,43 @@
package io.github.jwdeveloper.tiktok.protocol;
import org.jsoup.Jsoup;
import java.io.File;
import java.io.IOException;
public class ProtocolGenerator
{
public static void main(String[] args) {
// Path to the HTML file
File htmlFile = new File("C:\\Users\\ja\\IdeaProjects\\TikTokLiveJava\\Tools\\src\\main\\resources\\page.html");
try {
// Parse the HTML file with Jsoup
var doc = Jsoup.parse(htmlFile, "UTF-8");
// Find all script tags
var scriptTags = doc.select("script");
// Display all script tags
int counter = 1;
for (var scriptTag : scriptTags) {
String srcValue = scriptTag.attr("src");
if(!srcValue.contains("tiktok/webapp/main/webapp-live/"))
{
continue;
}
// Only print those script tags which have a 'src' attribute
if (!srcValue.isEmpty()) {
System.out.println("Script Tag " + counter + " src attribute: " + srcValue);
}
counter++;
}
} catch (IOException e) {
e.printStackTrace();
}
}
}

View File

@@ -1,4 +1,4 @@
package io.github.jwdeveloper.tiktok;
package io.github.jwdeveloper.tiktok.utils;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

View File

@@ -7,12 +7,13 @@
<groupId>io.github.jwdeveloper.tiktok</groupId>
<artifactId>TikTokLiveJava</artifactId>
<packaging>pom</packaging>
<version>1.0.0</version>
<version>0.0.14-Release</version>
<modules>
<module>API</module>
<module>Client</module>
<module>Tools</module>
<module>TestApplication</module>
<module>Tools-EventsCollector</module>
</modules>
<properties>
@@ -54,6 +55,7 @@
<configuration>
<shadedArtifactAttached>true</shadedArtifactAttached>
<shadedClassifierName>all</shadedClassifierName>
<createDependencyReducedPom>false</createDependencyReducedPom>
<minimizeJar>true</minimizeJar>
<artifactSet>
<includes>
@@ -64,8 +66,6 @@
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>**/proto/**</exclude>
<exclude>**/google/**</exclude>
<exclude>**/tiktokSchema.proto/**</exclude>
</excludes>
</filter>
@@ -99,6 +99,8 @@
<scope>test</scope>
</dependency>
</dependencies>
</project>