mirror of
https://github.com/jwdeveloper/TikTokLiveJava.git
synced 2026-02-27 08:49:40 -05:00
Changes:
onCustomEvent() <- registering custom events onMapping() <- custom mappings check out 'CustomMappingExample' more gifs has been added exceptions are more explicit
This commit is contained in:
@@ -26,15 +26,20 @@ package io.github.jwdeveloper.tiktok.exceptions;
|
||||
/**
|
||||
* Happens when incoming data from TikTok can not be mapped to TikTokEvent's
|
||||
*/
|
||||
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 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);
|
||||
public TikTokMessageMappingException(Class<?> inputClazz, Class<?> outputClass, String message) {
|
||||
super("Unable to handle mapping from class: " + inputClazz.getSimpleName() + " to class " + outputClass.getSimpleName() + ": " + message);
|
||||
}
|
||||
|
||||
public TikTokMessageMappingException(Class<?> inputClazz, String message, Throwable throwable) {
|
||||
super("Unable to handle mapping from class: " + inputClazz.getSimpleName() + ": " + message, throwable);
|
||||
}
|
||||
|
||||
public TikTokMessageMappingException(String message, Throwable throwable) {
|
||||
super( message, throwable);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,12 @@ import io.github.jwdeveloper.tiktok.data.events.websocket.TikTokWebsocketUnhandl
|
||||
|
||||
public interface EventsBuilder<T> {
|
||||
|
||||
T onCustomEvent(EventConsumer<CustomEvent> event);
|
||||
/**
|
||||
* Method used to register own custom events
|
||||
* @param eventClazz event class
|
||||
* @param event action
|
||||
*/
|
||||
<E extends TikTokEvent> T onCustomEvent(Class<E> eventClazz, EventConsumer<E> event);
|
||||
|
||||
T onRoomInfo(EventConsumer<TikTokRoomInfoEvent> event);
|
||||
|
||||
@@ -52,6 +57,7 @@ public interface EventsBuilder<T> {
|
||||
|
||||
|
||||
T onGiftCombo(EventConsumer<TikTokGiftComboEvent> event);
|
||||
|
||||
T onGift(EventConsumer<TikTokGiftEvent> event);
|
||||
|
||||
T onQuestion(EventConsumer<TikTokQuestionEvent> event);
|
||||
@@ -68,7 +74,7 @@ public interface EventsBuilder<T> {
|
||||
|
||||
T onShare(EventConsumer<TikTokShareEvent> event);
|
||||
|
||||
// T onChest(EventConsumer<TikTokChestEvent> event);
|
||||
// T onChest(EventConsumer<TikTokChestEvent> event);
|
||||
|
||||
T onLivePaused(EventConsumer<TikTokLivePausedEvent> event);
|
||||
|
||||
@@ -83,11 +89,10 @@ public interface EventsBuilder<T> {
|
||||
T onDisconnected(EventConsumer<TikTokDisconnectedEvent> event);
|
||||
|
||||
T onError(EventConsumer<TikTokErrorEvent> event);
|
||||
|
||||
T onEvent(EventConsumer<TikTokEvent> event);
|
||||
|
||||
|
||||
|
||||
|
||||
// TODO Figure out how those events works
|
||||
//T onLinkMicFanTicket(TikTokEventConsumer<TikTokLinkMicFanTicketEvent> event);
|
||||
|
||||
|
||||
@@ -23,20 +23,60 @@
|
||||
package io.github.jwdeveloper.tiktok.live.builder;
|
||||
|
||||
import io.github.jwdeveloper.tiktok.ClientSettings;
|
||||
import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent;
|
||||
import io.github.jwdeveloper.tiktok.listener.TikTokEventListener;
|
||||
import io.github.jwdeveloper.tiktok.live.LiveClient;
|
||||
import io.github.jwdeveloper.tiktok.mappers.TikTokMapper;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
public interface LiveClientBuilder extends EventsBuilder<LiveClientBuilder> {
|
||||
LiveClientBuilder configure(Consumer<ClientSettings> consumer);
|
||||
|
||||
/**
|
||||
* This method is triggered after default mappings are registered
|
||||
* It could be used to OVERRIDE behaviour of mappings and implement custom behaviour
|
||||
*
|
||||
* Be aware if for example you override WebcastGiftEvent, onGiftEvent() will not be working
|
||||
*
|
||||
* @param onCustomMappings lambda method
|
||||
* @return
|
||||
*/
|
||||
LiveClientBuilder onMapping(Consumer<TikTokMapper> onCustomMappings);
|
||||
|
||||
|
||||
/**
|
||||
* Configuration of client settings
|
||||
* @see ClientSettings
|
||||
* @param onConfigure
|
||||
* @return
|
||||
*/
|
||||
LiveClientBuilder configure(Consumer<ClientSettings> onConfigure);
|
||||
|
||||
/**
|
||||
* @see TikTokEventListener
|
||||
* Adding events listener class, its fancy way to register events without using lamda method
|
||||
* but actual method in class that implements TikTokEventListener
|
||||
* @return
|
||||
*/
|
||||
LiveClientBuilder addListener(TikTokEventListener listener);
|
||||
|
||||
/**
|
||||
*
|
||||
* @return LiveClient object
|
||||
*/
|
||||
LiveClient build();
|
||||
|
||||
/**
|
||||
*
|
||||
* @return LiveClient object and connects to TikTok live
|
||||
*/
|
||||
LiveClient buildAndConnect();
|
||||
|
||||
/**
|
||||
*
|
||||
* @return LiveClient object and connects to TikTok live asynchronously
|
||||
*/
|
||||
CompletableFuture<LiveClient> buildAndConnectAsync();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
package io.github.jwdeveloper.tiktok.mappers;
|
||||
|
||||
import com.google.protobuf.GeneratedMessageV3;
|
||||
import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
public interface TikTokMapper {
|
||||
|
||||
|
||||
/**
|
||||
* Triggered when `sourceClass` is mapped,
|
||||
* input is bytes that are coming from TikTok in `sourceClass` packet
|
||||
* output is TikTok event we want to create
|
||||
* <p>
|
||||
* bytesToEvent(WebcastGiftMessage.class, bytes ->
|
||||
* {
|
||||
* var giftMessage = WebcastGiftMessage.parseFrom(bytes);
|
||||
* var giftName = giftMessage.getGift().getName();
|
||||
* return new TikTokEvent(Gift.ROSE, giftMessage);
|
||||
* })
|
||||
*
|
||||
* @param sourceClass protocol buffer webcast class
|
||||
* @param onMapping lambda function that is triggered on mapping. takes as input ProtocolBuffer object and as output TikTokEvent
|
||||
*/
|
||||
void bytesToEvent(Class<? extends GeneratedMessageV3> sourceClass, Function<byte[], TikTokEvent> onMapping);
|
||||
|
||||
void bytesToEvents(Class<? extends GeneratedMessageV3> sourceClass, Function<byte[], List<TikTokEvent>> onMapping);
|
||||
|
||||
|
||||
/**
|
||||
* In case you found some TikTok message that has not Webcast class use this method
|
||||
*
|
||||
* @param messageName Name of TikTok data event
|
||||
* @param onMapping lambda function that is triggered on mapping. takes as input ProtocolBuffer object and as output TikTokEvent
|
||||
*/
|
||||
void bytesToEvent(String messageName, Function<byte[], TikTokEvent> onMapping);
|
||||
|
||||
void bytesToEvents(String messageName, Function<byte[], List<TikTokEvent>> onMapping);
|
||||
|
||||
|
||||
/**
|
||||
* This method can be used to override default mapping for
|
||||
* certain TikTok incoming data message. For this example
|
||||
* we are overriding WebcastGiftMessage and retuning CustomGiftEvent
|
||||
* instead of TikTokGiftEvent
|
||||
* <p>
|
||||
* webcastObjectToEvent(WebcastGiftMessage.class, webcastGiftMessage ->
|
||||
* {
|
||||
* var giftName = webcastGiftMessage.getGift().getName();
|
||||
* var user = webcastGiftMessage.getUser().getNickname();
|
||||
* return new CustomGiftEvent(giftName, user);
|
||||
* })
|
||||
*
|
||||
* @param sourceClass ProtocolBuffer class that represent incoming custom data, hint class should starts with Webcast prefix
|
||||
* @param onMapping lambda function that is triggered on mapping. takes as input ProtocolBuffer object and as output TikTokEvent
|
||||
*/
|
||||
<T extends GeneratedMessageV3> void webcastObjectToEvent(Class<T> sourceClass, Function<T, TikTokEvent> onMapping);
|
||||
|
||||
<T extends GeneratedMessageV3> void webcastObjectToEvents(Class<T> sourceClass, Function<T, List<TikTokEvent>> onMapping);
|
||||
|
||||
/**
|
||||
* Triggered when `sourceClass` is mapped,
|
||||
* looking for constructor in `outputClass` with one parameter that is of type `sourceClass`
|
||||
* and created instance of object from this constructor
|
||||
*
|
||||
* @param sourceClass protocol buffer webcast class
|
||||
* @param outputClass TikTok event class
|
||||
*/
|
||||
void webcastObjectToConstructor(Class<? extends GeneratedMessageV3> sourceClass, Class<? extends TikTokEvent> outputClass);
|
||||
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2023-2023 jwdeveloper jacekwoln@gmail.com
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package io.github.jwdeveloper.tiktok.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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package io.github.jwdeveloper.tiktok.utils;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
public class ProtoBufferFileGenerator {
|
||||
|
||||
|
||||
public static String generate(ProtoBufferObject protoBuffObj, String name) {
|
||||
|
||||
var sb = new StringBuilder();
|
||||
var offset = 2;
|
||||
sb.append("message ").append(name).append(" { \n");
|
||||
|
||||
var objects = new TreeMap<String, ProtoBufferObject>();
|
||||
var objectCounter = 0;
|
||||
for (var entry : protoBuffObj.getFields().entrySet()) {
|
||||
var index = entry.getKey();
|
||||
var field = entry.getValue();
|
||||
var fieldName = field.type.toLowerCase() + "Value";
|
||||
var value = field.value;
|
||||
if (field.value instanceof ProtoBufferObject object) {
|
||||
fieldName += objectCounter;
|
||||
value = "";
|
||||
objects.put(fieldName,object);
|
||||
objectCounter++;
|
||||
|
||||
}
|
||||
for (var i = 0; i < offset; i++) {
|
||||
sb.append(" ");
|
||||
}
|
||||
sb.append(field.type).append(" ").append(fieldName)
|
||||
.append(" ")
|
||||
.append("=")
|
||||
.append(" ")
|
||||
.append(index)
|
||||
.append(";")
|
||||
.append(" //")
|
||||
.append(value)
|
||||
.append("\n");
|
||||
}
|
||||
sb.append(" \n");
|
||||
for(var object : objects.entrySet())
|
||||
{
|
||||
var structure = generate(object.getValue(),object.getKey());
|
||||
sb.append(structure);
|
||||
}
|
||||
|
||||
|
||||
sb.append(" \n");
|
||||
sb.append("} \n");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package io.github.jwdeveloper.tiktok.utils;
|
||||
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
public class ProtoBufferJsonGenerator {
|
||||
public static JsonObject genratejson(ProtoBufferObject protoBuffObj) {
|
||||
|
||||
JsonObject jsonObject = new JsonObject();
|
||||
for (var entry : protoBuffObj.getFields().entrySet()) {
|
||||
var fieldName = entry.getKey() + "_" + entry.getValue().type;
|
||||
if (entry.getValue().value instanceof ProtoBufferObject protoObj)
|
||||
{
|
||||
JsonObject childJson = genratejson(protoObj);
|
||||
jsonObject.add(fieldName, childJson);
|
||||
continue;
|
||||
}
|
||||
|
||||
var value = entry.getValue().value.toString();
|
||||
jsonObject.addProperty(fieldName, value);
|
||||
}
|
||||
|
||||
return jsonObject;
|
||||
}
|
||||
|
||||
public static String generate(ProtoBufferObject protoBufferObject) {
|
||||
var json = genratejson(protoBufferObject);
|
||||
var gson = new GsonBuilder().setPrettyPrinting().create();
|
||||
return gson.toJson(json);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package io.github.jwdeveloper.tiktok.utils;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
public class ProtoBufferObject {
|
||||
@Getter
|
||||
private final Map<Integer, ProtoBufferField> fields;
|
||||
|
||||
public ProtoBufferObject() {
|
||||
this.fields = new TreeMap<>();
|
||||
}
|
||||
|
||||
public void addField(int index, String type, Object value) {
|
||||
fields.put(index, new ProtoBufferField(type, value));
|
||||
}
|
||||
|
||||
public void addField(int index, ProtoBufferField value) {
|
||||
fields.put(index, value);
|
||||
}
|
||||
|
||||
|
||||
public String toProtoFile()
|
||||
{
|
||||
return ProtoBufferFileGenerator.generate(this,"UnknownMessage");
|
||||
}
|
||||
|
||||
public String toJson()
|
||||
{
|
||||
return ProtoBufferJsonGenerator.generate(this);
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
return toString(0, true);
|
||||
}
|
||||
|
||||
public String toString(int offset ,boolean nested) {
|
||||
|
||||
var sb = new StringBuilder();
|
||||
sb.append("\n");
|
||||
for (var entry : fields.entrySet()) {
|
||||
var index = entry.getKey();
|
||||
var field = entry.getValue();
|
||||
|
||||
for(var i =0;i<offset;i++)
|
||||
{
|
||||
sb.append(" ");
|
||||
}
|
||||
sb.append(index).append(" ")
|
||||
.append(field.type).append(" ");
|
||||
|
||||
var value = field.value;
|
||||
if (value instanceof ProtoBufferObject child) {
|
||||
sb.append(child.toString(offset+2,nested));
|
||||
} else {
|
||||
sb.append(entry.getValue().value);
|
||||
}
|
||||
|
||||
sb.append("\n");
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@AllArgsConstructor
|
||||
public class ProtoBufferField {
|
||||
public String type;
|
||||
|
||||
public Object value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package io.github.jwdeveloper.tiktok.utils;
|
||||
|
||||
import com.google.protobuf.ByteString;
|
||||
import com.google.protobuf.InvalidProtocolBufferException;
|
||||
import com.google.protobuf.UnknownFieldSet;
|
||||
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastResponse;
|
||||
|
||||
import java.util.Base64;
|
||||
|
||||
public class ProtocolUtils {
|
||||
public static String toBase64(byte[] bytes) {
|
||||
|
||||
return Base64.getEncoder().encodeToString(bytes);
|
||||
}
|
||||
|
||||
public static String toBase64(WebcastResponse.Message bytes) {
|
||||
return Base64.getEncoder().encodeToString(bytes.toByteArray());
|
||||
}
|
||||
|
||||
|
||||
public static byte[] fromBase64(String base64) {
|
||||
|
||||
return Base64.getDecoder().decode(base64);
|
||||
}
|
||||
|
||||
public static ProtoBufferObject getProtocolBufferStructure(byte[] bytes) {
|
||||
|
||||
try {
|
||||
var files = UnknownFieldSet.parseFrom(bytes);
|
||||
var protoBufferObject = new ProtoBufferObject();
|
||||
createStructure(files, protoBufferObject);
|
||||
return protoBufferObject;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void createStructure(UnknownFieldSet unknownFieldSet, ProtoBufferObject root) throws InvalidProtocolBufferException {
|
||||
for (var entry : unknownFieldSet.asMap().entrySet()) {
|
||||
var objectValue = entry.getValue();
|
||||
var type = "undefind";
|
||||
Object value = null;
|
||||
var index = entry.getKey();
|
||||
if (!objectValue.getLengthDelimitedList().isEmpty()) {
|
||||
var nestedObject = new ProtoBufferObject();
|
||||
for (var str : objectValue.getLengthDelimitedList()) {
|
||||
try {
|
||||
var nestedFieldsSet = UnknownFieldSet.parseFrom(str);
|
||||
createStructure(nestedFieldsSet, nestedObject);
|
||||
} catch (Exception e)
|
||||
{
|
||||
type = "string";
|
||||
value = str.toStringUtf8();
|
||||
}
|
||||
}
|
||||
if (value != null) {
|
||||
root.addField(index, "string", value);
|
||||
} else {
|
||||
root.addField(index, "object", nestedObject);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!objectValue.getFixed32List().isEmpty()) {
|
||||
type = "Fixed32List";
|
||||
value = objectValue.getFixed32List();
|
||||
}
|
||||
|
||||
if (!objectValue.getFixed64List().isEmpty()) {
|
||||
type = "Fixed64List";
|
||||
value = objectValue.getFixed64List();
|
||||
}
|
||||
|
||||
if (!objectValue.getGroupList().isEmpty()) {
|
||||
type = "getGroupList";
|
||||
value = objectValue.getGroupList();
|
||||
}
|
||||
|
||||
if (!objectValue.getVarintList().isEmpty()) {
|
||||
type = "int";
|
||||
value = objectValue.getVarintList().get(0);
|
||||
}
|
||||
|
||||
root.addField(index, type, value);
|
||||
}
|
||||
}
|
||||
|
||||
public static WebcastResponse.Message fromBase64ToMessage(String base64) throws InvalidProtocolBufferException {
|
||||
var bytes = fromBase64(base64);
|
||||
return WebcastResponse.Message.parseFrom(bytes);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -177,6 +177,8 @@ message WebcastCaptionMessage {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Comment sent by User
|
||||
//@WebcastChatMessage
|
||||
message WebcastChatMessage {
|
||||
|
||||
Reference in New Issue
Block a user