init v1.0.0

This commit is contained in:
ageer
2024-02-27 20:52:19 +08:00
parent 1f7f97e86a
commit a079ef44e5
602 changed files with 163057 additions and 95 deletions

View File

@@ -0,0 +1,99 @@
/*
* MIT License
*
* Copyright (c) 2023 OrdinaryRoad
*
* 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 tech.ordinaryroad.live.chat.client.kuaishou;
import cn.hutool.core.util.RandomUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import tech.ordinaryroad.live.chat.client.commons.base.msg.ICmdMsg;
import tech.ordinaryroad.live.chat.client.commons.base.msg.IMsg;
import tech.ordinaryroad.live.chat.client.commons.client.enums.ClientStatusEnums;
import tech.ordinaryroad.live.chat.client.kuaishou.client.KuaishouLiveChatClient;
import tech.ordinaryroad.live.chat.client.kuaishou.config.KuaishouLiveChatClientConfig;
import tech.ordinaryroad.live.chat.client.kuaishou.listener.IKuaishouMsgListener;
import tech.ordinaryroad.live.chat.client.kuaishou.msg.KuaishouDanmuMsg;
import tech.ordinaryroad.live.chat.client.kuaishou.msg.KuaishouGiftMsg;
import tech.ordinaryroad.live.chat.client.kuaishou.netty.handler.KuaishouBinaryFrameHandler;
import tech.ordinaryroad.live.chat.client.kuaishou.protobuf.PayloadTypeOuterClass;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
public class ClientModeExample {
static Logger log = LoggerFactory.getLogger(ClientModeExample.class);
public static void main(String[] args) {
// 1. 创建配置
KuaishouLiveChatClientConfig config = KuaishouLiveChatClientConfig.builder()
// TODO 浏览器Cookie
.cookie("did=web_6c4ac2a8ef8855d35df4d564baeaa8e8; kuaishou.live.bfb1s=7206d814e5c089a58c910ed8bf52ace5; clientid=3; did=web_6c4ac2a8ef8855d35df4d564baeaa8e8; client_key=65890b29; kpn=GAME_ZONE; userId=3941614875; kuaishou.live.web_st=ChRrdWFpc2hvdS5saXZlLndlYi5zdBKgAbRhgemDxM_Z_lBn7EZ_-5unvtslsh7ci5VY_eg80qqjxy5yb7oBFrdcWSPlz6jMIBz9h_yoLzATE3ngj2WawpxvbJhmq0EnRYIHv308kTBmg4KN2JQf7w2mfrsp1vusFbZ3NkfsEO4PrGQfX0L6mJRbgXeBqyz4tUM5O0q2Jte_NzWkaOnezvIGRAG8Y6yA1dxGUmiA9syTrPrdnSOzZvAaEoJNhwQ4OUDtgURWN6k9Xgm8PSIgAfV-ZvahtgaYhopZno6OuS2pkaFZjrz4ymoEZ1DSnj0oBTAB; kuaishou.live.web_ph=a287be6ab01dce264c0554eed94c2d6ac991; userId=3941614875")
// TODO 直播间id支持短id
.roomId("tianci666")
.build();
// 2. 创建Client并传入配置、添加消息回调
KuaishouLiveChatClient client = new KuaishouLiveChatClient(config, new IKuaishouMsgListener() {
@Override
public void onMsg(IMsg msg) {
log.debug("收到{}消息 {}", msg.getClass(), msg);
}
@Override
public void onCmdMsg(PayloadTypeOuterClass.PayloadType cmd, ICmdMsg<PayloadTypeOuterClass.PayloadType> cmdMsg) {
log.debug("收到CMD消息{} {}", cmd, cmdMsg);
}
@Override
public void onOtherCmdMsg(PayloadTypeOuterClass.PayloadType cmd, ICmdMsg<PayloadTypeOuterClass.PayloadType> cmdMsg) {
log.debug("收到其他CMD消息 {}", cmd);
}
@Override
public void onUnknownCmd(String cmdString, IMsg msg) {
log.debug("收到未知CMD消息 {}", cmdString);
}
@Override
public void onDanmuMsg(KuaishouBinaryFrameHandler binaryFrameHandler, KuaishouDanmuMsg msg) {
log.info("{} 收到弹幕 [{}] {}({}){}", binaryFrameHandler.getRoomId(), msg.getBadgeLevel() != 0 ? msg.getBadgeLevel() + msg.getBadgeName() : "", msg.getUsername(), msg.getUid(), msg.getContent());
}
@Override
public void onGiftMsg(KuaishouBinaryFrameHandler binaryFrameHandler, KuaishouGiftMsg msg) {
log.info("{} 收到礼物 [{}] {}({}) {} {}({})x{}({}) mergeKey:{},comboCount:{}, batchSize:{}", binaryFrameHandler.getRoomId(), msg.getBadgeLevel() != 0 ? msg.getBadgeLevel() + msg.getBadgeName() : "", msg.getUsername(), msg.getUid(), "赠送", msg.getGiftName(), msg.getGiftId(), msg.getGiftCount(), msg.getGiftPrice(), msg.getMsg().getMergeKey(), msg.getMsg().getComboCount(), msg.getMsg().getBatchSize());
}
});
// 3. 开始监听直播间
client.connect();
// 客户端连接状态回调
client.addStatusChangeListener(evt -> {
if (evt.getNewValue().equals(ClientStatusEnums.CONNECTED)) {
// TODO 要发送的弹幕内容,请注意控制发送频率;框架内置支持设置发送弹幕的最少时间间隔,小于时将忽略该次发送
client.sendDanmu("666666" + RandomUtil.randomNumbers(1));
}
});
}
}

View File

@@ -0,0 +1,235 @@
/*
* MIT License
*
* Copyright (c) 2023 OrdinaryRoad
*
* 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 tech.ordinaryroad.live.chat.client.kuaishou.api;
import cn.hutool.cache.impl.TimedCache;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ReUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import cn.hutool.http.*;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import lombok.*;
import tech.ordinaryroad.live.chat.client.commons.base.exception.BaseException;
import tech.ordinaryroad.live.chat.client.commons.util.OrLiveChatCookieUtil;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import static tech.ordinaryroad.live.chat.client.commons.base.msg.BaseMsg.OBJECT_MAPPER;
/**
* @author mjz
* @date 2024/1/5
*/
public class KuaishouApis {
/**
* 接口返回结果缓存
* {@link #KEY_RESULT_CACHE_GIFT_ITEMS}:所有礼物信息
*/
public static final TimedCache<String, Map<String, GiftInfo>> RESULT_CACHE = new TimedCache<>(TimeUnit.DAYS.toMillis(1));
public static final String KEY_RESULT_CACHE_GIFT_ITEMS = "GIFT_ITEMS";
public static final String PATTERN_LIVE_STREAM_ID = "\"liveStream\":\\{\"id\":\"([\\w\\d-_]+)\"";
public static final String USER_AGENT = GlobalHeaders.INSTANCE.header(Header.USER_AGENT).replace("Hutool", "");
public static RoomInitResult roomInit(Object roomId, String cookie) {
@Cleanup
HttpResponse response = createGetRequest("https://live.kuaishou.com/u/" + roomId, cookie)
.execute();
if (StrUtil.isBlank(cookie)) {
cookie = OrLiveChatCookieUtil.toString(response.getCookies());
}
String body = response.body();
String liveStreamId = ReUtil.getGroup1(PATTERN_LIVE_STREAM_ID, body);
JsonNode websocketinfo = websocketinfo(roomId, liveStreamId, cookie);
if (!websocketinfo.has("token")) {
throwExceptionWithTip("主播未开播token获取失败 " + websocketinfo);
}
ArrayNode websocketUrls = websocketinfo.withArray("websocketUrls");
ArrayList<String> websocketUrlList = CollUtil.newArrayList();
for (JsonNode websocketUrl : websocketUrls) {
websocketUrlList.add(websocketUrl.asText());
}
return RoomInitResult.builder()
.token(websocketinfo.required("token").asText())
.websocketUrls(websocketUrlList)
.liveStreamId(liveStreamId)
.build();
}
public static RoomInitResult roomInit(Object roomId) {
return roomInit(roomId, null);
}
public static JsonNode websocketinfo(Object roomId, String liveStreamId, String cookie) {
if (StrUtil.isBlank(liveStreamId)) {
throwExceptionWithTip("主播未开播liveStreamId为空");
}
@Cleanup
HttpResponse response = createGetRequest("https://live.kuaishou.com/live_api/liveroom/websocketinfo?liveStreamId=" + liveStreamId, cookie)
.header(Header.REFERER, "https://live.kuaishou.com/u/" + roomId)
.execute();
return responseInterceptor(response.body());
}
public static Map<String, GiftInfo> allgifts() {
Map<String, GiftInfo> map = new HashMap<>();
@Cleanup
HttpResponse response = createGetRequest("https://live.kuaishou.com/live_api/emoji/allgifts", null).execute();
JsonNode jsonNode = responseInterceptor(response.body());
jsonNode.fields().forEachRemaining(new Consumer<Map.Entry<String, JsonNode>>() {
@Override
public void accept(Map.Entry<String, JsonNode> stringJsonNodeEntry) {
map.put(stringJsonNodeEntry.getKey(), OBJECT_MAPPER.convertValue(stringJsonNodeEntry.getValue(), GiftInfo.class));
}
});
return map;
}
/**
* 根据礼物ID获取礼物信息
*
* @param id 礼物ID
* @return 礼物信息
*/
public static GiftInfo getGiftInfoById(String id) {
if (!RESULT_CACHE.containsKey(KEY_RESULT_CACHE_GIFT_ITEMS)) {
RESULT_CACHE.put(KEY_RESULT_CACHE_GIFT_ITEMS, allgifts());
}
return RESULT_CACHE.get(KEY_RESULT_CACHE_GIFT_ITEMS).get(id);
}
@SneakyThrows
public static JsonNode sendComment(String cookie, Object roomId, SendCommentRequest request) {
@Cleanup
HttpResponse response = createPostRequest("https://live.kuaishou.com/live_api/liveroom/sendComment", cookie)
.body(OBJECT_MAPPER.writeValueAsString(request), ContentType.JSON.getValue())
.header(Header.REFERER, "https://live.kuaishou.com/u/" + roomId)
.execute();
return responseInterceptor(response.body());
}
@SneakyThrows
public static JsonNode clickLike(String cookie, Object roomId, String liveStreamId, int count) {
@Cleanup
HttpResponse response = createPostRequest("https://live.kuaishou.com/live_api/liveroom/like", cookie)
.body(OBJECT_MAPPER.createObjectNode()
.put("liveStreamId", liveStreamId)
.put("count", count)
.toString(), ContentType.JSON.getValue()
)
.header(Header.ORIGIN, "https://live.kuaishou.com")
.header(Header.REFERER, "https://live.kuaishou.com/u/" + roomId)
.execute();
return responseInterceptor(response.body());
}
public static HttpRequest createRequest(Method method, String url, String cookie) {
return HttpUtil.createRequest(method, url)
.cookie(cookie)
.header(Header.HOST, URLUtil.url(url).getHost())
.header(Header.USER_AGENT, USER_AGENT);
}
public static HttpRequest createGetRequest(String url, String cookie) {
return createRequest(Method.GET, url, cookie);
}
public static HttpRequest createPostRequest(String url, String cookie) {
return createRequest(Method.POST, url, cookie);
}
private static JsonNode responseInterceptor(String responseString) {
try {
JsonNode jsonNode = OBJECT_MAPPER.readTree(responseString);
JsonNode data = jsonNode.required("data");
if (data.has("result")) {
int result = data.get("result").asInt();
if (result != 1) {
String message = "";
switch (result) {
case 2: {
message = "请求过快,请稍后重试";
break;
}
case 400002: {
message = "需要进行验证";
break;
}
default: {
message = "";
}
}
throwExceptionWithTip("接口访问失败:" + message + ",返回结果:" + jsonNode);
}
}
return data;
} catch (JsonProcessingException e) {
throw new BaseException(e);
}
}
private static void throwExceptionWithTip(String message) {
throw new BaseException("『可能已触发滑块验证建议配置Cookie或打开浏览器进行滑块验证后重试』" + message);
}
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public static class SendCommentRequest {
private String liveStreamId;
private String content;
private String color;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public static class RoomInitResult {
private String token;
private String liveStreamId;
private List<String> websocketUrls;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class GiftInfo {
private String giftName;
private String giftUrl;
}
}

View File

@@ -0,0 +1,201 @@
/*
* MIT License
*
* Copyright (c) 2023 OrdinaryRoad
*
* 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 tech.ordinaryroad.live.chat.client.kuaishou.client;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.RandomUtil;
import com.fasterxml.jackson.databind.JsonNode;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.handler.codec.http.DefaultHttpHeaders;
import io.netty.handler.codec.http.websocketx.WebSocketClientHandshakerFactory;
import io.netty.handler.codec.http.websocketx.WebSocketVersion;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import tech.ordinaryroad.live.chat.client.commons.base.exception.BaseException;
import tech.ordinaryroad.live.chat.client.commons.base.listener.IBaseConnectionListener;
import tech.ordinaryroad.live.chat.client.kuaishou.api.KuaishouApis;
import tech.ordinaryroad.live.chat.client.kuaishou.config.KuaishouLiveChatClientConfig;
import tech.ordinaryroad.live.chat.client.kuaishou.listener.IKuaishouConnectionListener;
import tech.ordinaryroad.live.chat.client.kuaishou.listener.IKuaishouMsgListener;
import tech.ordinaryroad.live.chat.client.kuaishou.msg.base.IKuaishouMsg;
import tech.ordinaryroad.live.chat.client.kuaishou.netty.handler.KuaishouBinaryFrameHandler;
import tech.ordinaryroad.live.chat.client.kuaishou.netty.handler.KuaishouConnectionHandler;
import tech.ordinaryroad.live.chat.client.kuaishou.protobuf.PayloadTypeOuterClass;
import tech.ordinaryroad.live.chat.client.servers.netty.client.base.BaseNettyClient;
import java.util.List;
import java.util.function.Consumer;
/**
* @author mjz
* @date 2024/1/5
*/
@Slf4j
public class KuaishouLiveChatClient extends BaseNettyClient<
KuaishouLiveChatClientConfig,
PayloadTypeOuterClass.PayloadType,
IKuaishouMsg,
IKuaishouMsgListener,
KuaishouConnectionHandler,
KuaishouBinaryFrameHandler> {
@Getter
KuaishouApis.RoomInitResult roomInitResult = new KuaishouApis.RoomInitResult();
public KuaishouLiveChatClient(KuaishouLiveChatClientConfig config, List<IKuaishouMsgListener> msgListeners, IKuaishouConnectionListener connectionListener, EventLoopGroup workerGroup) {
super(config, workerGroup, connectionListener);
addMsgListeners(msgListeners);
// 初始化
this.init();
}
public KuaishouLiveChatClient(KuaishouLiveChatClientConfig config, IKuaishouMsgListener msgListener, IKuaishouConnectionListener connectionListener, EventLoopGroup workerGroup) {
super(config, workerGroup, connectionListener);
addMsgListener(msgListener);
// 初始化
this.init();
}
public KuaishouLiveChatClient(KuaishouLiveChatClientConfig config, IKuaishouMsgListener msgListener, IKuaishouConnectionListener connectionListener) {
this(config, msgListener, connectionListener, new NioEventLoopGroup());
}
public KuaishouLiveChatClient(KuaishouLiveChatClientConfig config, IKuaishouMsgListener msgListener) {
this(config, msgListener, null, new NioEventLoopGroup());
}
public KuaishouLiveChatClient(KuaishouLiveChatClientConfig config) {
this(config, null);
}
@Override
public void init() {
roomInitResult = KuaishouApis.roomInit(getConfig().getRoomId(), getConfig().getCookie());
super.init();
}
@Override
protected String getWebSocketUriString() {
List<String> websocketUrls = roomInitResult.getWebsocketUrls();
return CollUtil.get(websocketUrls, RandomUtil.randomInt(0, websocketUrls.size()));
}
@Override
public KuaishouConnectionHandler initConnectionHandler(IBaseConnectionListener<KuaishouConnectionHandler> clientConnectionListener) {
return new KuaishouConnectionHandler(
WebSocketClientHandshakerFactory.newHandshaker(getWebsocketUri(), WebSocketVersion.V13, null, true, new DefaultHttpHeaders(), getConfig().getMaxFramePayloadLength()),
KuaishouLiveChatClient.this, clientConnectionListener
);
}
@Override
public KuaishouBinaryFrameHandler initBinaryFrameHandler() {
return new KuaishouBinaryFrameHandler(super.msgListeners, KuaishouLiveChatClient.this);
}
@Override
public void sendDanmu(Object danmu, Runnable success, Consumer<Throwable> failed) {
if (!checkCanSendDanmu()) {
return;
}
if (danmu instanceof String) {
String msg = (String) danmu;
try {
if (log.isDebugEnabled()) {
log.debug("{} kuaishou发送弹幕 {}", getConfig().getRoomId(), danmu);
}
boolean sendSuccess = false;
try {
KuaishouApis.sendComment(getConfig().getCookie(),
getConfig().getRoomId(),
KuaishouApis.SendCommentRequest.builder()
.liveStreamId(roomInitResult.getLiveStreamId())
.content(msg)
.build()
);
sendSuccess = true;
} catch (Exception e) {
log.error("kuaishou弹幕发送失败", e);
if (failed != null) {
failed.accept(e);
}
}
if (!sendSuccess) {
return;
}
if (log.isDebugEnabled()) {
log.debug("kuaishou弹幕发送成功 {}", danmu);
}
if (success != null) {
success.run();
}
finishSendDanmu();
} catch (Exception e) {
log.error("kuaishou弹幕发送失败", e);
if (failed != null) {
failed.accept(e);
}
}
} else {
super.sendDanmu(danmu, success, failed);
}
}
@Override
public void clickLike(int count, Runnable success, Consumer<Throwable> failed) {
if (count <= 0) {
throw new BaseException("点赞次数必须大于0");
}
boolean successfullyClicked = false;
try {
JsonNode jsonNode = KuaishouApis.clickLike(getConfig().getCookie(), getConfig().getRoomId(), roomInitResult.getLiveStreamId(), count);
if (jsonNode.asBoolean()) {
successfullyClicked = true;
}
} catch (Exception e) {
log.error("kuaishou为直播间点赞失败", e);
if (failed != null) {
failed.accept(e);
}
}
if (!successfullyClicked) {
return;
}
if (log.isDebugEnabled()) {
log.debug("kuaishou为直播间点赞成功");
}
if (success != null) {
success.run();
}
}
}

View File

@@ -0,0 +1,47 @@
/*
* MIT License
*
* Copyright (c) 2023 OrdinaryRoad
*
* 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 tech.ordinaryroad.live.chat.client.kuaishou.config;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
import tech.ordinaryroad.live.chat.client.servers.netty.client.config.BaseNettyClientConfig;
/**
* @author mjz
* @date 2024/1/5
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@SuperBuilder(toBuilder = true)
public class KuaishouLiveChatClientConfig extends BaseNettyClientConfig {
@Builder.Default
private long heartbeatPeriod = 20;
}

View File

@@ -0,0 +1,35 @@
/*
* MIT License
*
* Copyright (c) 2023 OrdinaryRoad
*
* 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 tech.ordinaryroad.live.chat.client.kuaishou.listener;
import tech.ordinaryroad.live.chat.client.commons.base.listener.IBaseConnectionListener;
import tech.ordinaryroad.live.chat.client.kuaishou.netty.handler.KuaishouConnectionHandler;
/**
* @author mjz
* @date 2024/1/5
*/
public interface IKuaishouConnectionListener extends IBaseConnectionListener<KuaishouConnectionHandler> {
}

View File

@@ -0,0 +1,45 @@
/*
* MIT License
*
* Copyright (c) 2023 OrdinaryRoad
*
* 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 tech.ordinaryroad.live.chat.client.kuaishou.listener;
import tech.ordinaryroad.live.chat.client.commons.base.listener.IBaseMsgListener;
import tech.ordinaryroad.live.chat.client.commons.base.listener.IDanmuMsgListener;
import tech.ordinaryroad.live.chat.client.commons.base.listener.IGiftMsgListener;
import tech.ordinaryroad.live.chat.client.commons.base.listener.ILikeMsgListener;
import tech.ordinaryroad.live.chat.client.kuaishou.msg.KuaishouDanmuMsg;
import tech.ordinaryroad.live.chat.client.kuaishou.msg.KuaishouGiftMsg;
import tech.ordinaryroad.live.chat.client.kuaishou.msg.KuaishouLikeMsg;
import tech.ordinaryroad.live.chat.client.kuaishou.netty.handler.KuaishouBinaryFrameHandler;
import tech.ordinaryroad.live.chat.client.kuaishou.protobuf.PayloadTypeOuterClass;
/**
* @author mjz
* @date 2024/1/5
*/
public interface IKuaishouMsgListener extends IBaseMsgListener<KuaishouBinaryFrameHandler, PayloadTypeOuterClass.PayloadType>,
IDanmuMsgListener<KuaishouBinaryFrameHandler, KuaishouDanmuMsg>,
IGiftMsgListener<KuaishouBinaryFrameHandler, KuaishouGiftMsg>,
ILikeMsgListener<KuaishouBinaryFrameHandler, KuaishouLikeMsg> {
}

View File

@@ -0,0 +1,100 @@
/*
* MIT License
*
* Copyright (c) 2023 OrdinaryRoad
*
* 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 tech.ordinaryroad.live.chat.client.kuaishou.msg;
import com.google.protobuf.ByteString;
import com.google.protobuf.UnknownFieldSet;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import tech.ordinaryroad.live.chat.client.commons.base.msg.IDanmuMsg;
import tech.ordinaryroad.live.chat.client.kuaishou.msg.base.IKuaishouMsg;
import tech.ordinaryroad.live.chat.client.kuaishou.protobuf.WebCommentFeedOuterClass;
import java.util.List;
/**
* @author mjz
* @date 2024/1/9
*/
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class KuaishouDanmuMsg implements IKuaishouMsg, IDanmuMsg {
private WebCommentFeedOuterClass.WebCommentFeed msg;
@Override
public String getBadgeName() {
String badgeName = null;
try {
UnknownFieldSet.Field field21 = msg.getSenderState().getUnknownFields().asMap().get(21);
List<ByteString> lengthDelimitedList = field21.getLengthDelimitedList();
if (!lengthDelimitedList.isEmpty()) {
UnknownFieldSet.Field field21_1 = UnknownFieldSet.parseFrom(
lengthDelimitedList.size() > 1 ? lengthDelimitedList.get(1) : lengthDelimitedList.get(0)
).getField(1);
List<ByteString> lengthDelimitedList1 = field21_1.getLengthDelimitedList();
if (!lengthDelimitedList1.isEmpty()) {
UnknownFieldSet.Field field21_1_4 = UnknownFieldSet.parseFrom((lengthDelimitedList1.get(0))).getField(4);
List<ByteString> lengthDelimitedList2 = field21_1_4.getLengthDelimitedList();
if (!lengthDelimitedList2.isEmpty()) {
badgeName = lengthDelimitedList2.get(0).toStringUtf8();
}
}
}
} catch (Exception e) {
// ignore
}
return badgeName;
}
@Override
public byte getBadgeLevel() {
return (byte) msg.getSenderState().getLiveFansGroupState().getIntimacyLevel();
}
@Override
public String getUid() {
return msg.getUser().getPrincipalId();
}
@Override
public String getUsername() {
return msg.getUser().getUserName();
}
@Override
public String getUserAvatar() {
return msg.getUser().getHeadUrl();
}
@Override
public String getContent() {
return msg.getContent();
}
}

View File

@@ -0,0 +1,108 @@
/*
* MIT License
*
* Copyright (c) 2023 OrdinaryRoad
*
* 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 tech.ordinaryroad.live.chat.client.kuaishou.msg;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import tech.ordinaryroad.live.chat.client.commons.base.msg.IGiftMsg;
import tech.ordinaryroad.live.chat.client.kuaishou.api.KuaishouApis;
import tech.ordinaryroad.live.chat.client.kuaishou.msg.base.IKuaishouMsg;
import tech.ordinaryroad.live.chat.client.kuaishou.protobuf.WebGiftFeedOuterClass;
/**
* @author mjz
* @date 2024/1/9
*/
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class KuaishouGiftMsg implements IKuaishouMsg, IGiftMsg {
private WebGiftFeedOuterClass.WebGiftFeed msg;
@Override
public String getBadgeName() {
return IGiftMsg.super.getBadgeName();
}
@Override
public byte getBadgeLevel() {
return IGiftMsg.super.getBadgeLevel();
}
@Override
public String getUid() {
return msg.getUser().getPrincipalId();
}
@Override
public String getUsername() {
return msg.getUser().getUserName();
}
@Override
public String getUserAvatar() {
return msg.getUser().getHeadUrl();
}
@Override
public String getGiftName() {
return KuaishouApis.getGiftInfoById(this.getGiftId()).getGiftName();
}
@Override
public String getGiftImg() {
return KuaishouApis.getGiftInfoById(this.getGiftId()).getGiftUrl();
}
@Override
public String getGiftId() {
return Integer.toString(msg.getIntGiftId());
}
@Override
public int getGiftCount() {
// TODO 礼物个数?网页上只显示赠送了什么东西,不显示个数,只会显示连击
return 0;
}
@Override
public int getGiftPrice() {
return 0;
}
@Override
public String getReceiveUid() {
return null;
}
@Override
public String getReceiveUsername() {
return null;
}
}

View File

@@ -0,0 +1,61 @@
/*
* MIT License
*
* Copyright (c) 2023 OrdinaryRoad
*
* 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 tech.ordinaryroad.live.chat.client.kuaishou.msg;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import tech.ordinaryroad.live.chat.client.commons.base.msg.ILikeMsg;
import tech.ordinaryroad.live.chat.client.kuaishou.msg.base.IKuaishouMsg;
import tech.ordinaryroad.live.chat.client.kuaishou.protobuf.WebLikeFeedOuterClass;
/**
* @author mjz
* @date 2024/1/9
*/
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class KuaishouLikeMsg implements IKuaishouMsg, ILikeMsg {
private WebLikeFeedOuterClass.WebLikeFeed msg;
@Override
public String getUid() {
return msg.getUser().getPrincipalId();
}
@Override
public String getUsername() {
return msg.getUser().getUserName();
}
@Override
public String getUserAvatar() {
return msg.getUser().getHeadUrl();
}
}

View File

@@ -0,0 +1,35 @@
/*
* MIT License
*
* Copyright (c) 2023 OrdinaryRoad
*
* 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 tech.ordinaryroad.live.chat.client.kuaishou.msg.base;
import tech.ordinaryroad.live.chat.client.commons.base.msg.ICmdMsg;
import tech.ordinaryroad.live.chat.client.kuaishou.protobuf.PayloadTypeOuterClass;
/**
* @author mjz
* @date 2024/1/5
*/
public interface IKuaishouCmdMsg extends IKuaishouMsg, ICmdMsg<PayloadTypeOuterClass.PayloadType> {
}

View File

@@ -0,0 +1,34 @@
/*
* MIT License
*
* Copyright (c) 2023 OrdinaryRoad
*
* 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 tech.ordinaryroad.live.chat.client.kuaishou.msg.base;
import tech.ordinaryroad.live.chat.client.commons.base.msg.IMsg;
/**
* @author mjz
* @date 2024/1/5
*/
public interface IKuaishouMsg extends IMsg {
}

View File

@@ -0,0 +1,127 @@
/*
* MIT License
*
* Copyright (c) 2023 OrdinaryRoad
*
* 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 tech.ordinaryroad.live.chat.client.kuaishou.netty.handler;
import cn.hutool.core.util.ZipUtil;
import com.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandler;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import tech.ordinaryroad.live.chat.client.commons.base.exception.BaseException;
import tech.ordinaryroad.live.chat.client.commons.base.msg.ICmdMsg;
import tech.ordinaryroad.live.chat.client.kuaishou.client.KuaishouLiveChatClient;
import tech.ordinaryroad.live.chat.client.kuaishou.listener.IKuaishouMsgListener;
import tech.ordinaryroad.live.chat.client.kuaishou.msg.KuaishouDanmuMsg;
import tech.ordinaryroad.live.chat.client.kuaishou.msg.KuaishouGiftMsg;
import tech.ordinaryroad.live.chat.client.kuaishou.msg.KuaishouLikeMsg;
import tech.ordinaryroad.live.chat.client.kuaishou.msg.base.IKuaishouMsg;
import tech.ordinaryroad.live.chat.client.kuaishou.protobuf.*;
import tech.ordinaryroad.live.chat.client.servers.netty.client.handler.BaseNettyClientBinaryFrameHandler;
import java.util.Collections;
import java.util.List;
/**
* @author mjz
* @date 2024/1/5
*/
@Slf4j
@ChannelHandler.Sharable
public class KuaishouBinaryFrameHandler extends BaseNettyClientBinaryFrameHandler<KuaishouLiveChatClient, KuaishouBinaryFrameHandler, PayloadTypeOuterClass.PayloadType, IKuaishouMsg, IKuaishouMsgListener> {
public KuaishouBinaryFrameHandler(List<IKuaishouMsgListener> iKuaishouMsgListeners, KuaishouLiveChatClient client) {
super(iKuaishouMsgListeners, client);
}
public KuaishouBinaryFrameHandler(List<IKuaishouMsgListener> iKuaishouMsgListeners, long roomId) {
super(iKuaishouMsgListeners, roomId);
}
@SneakyThrows
@Override
public void onCmdMsg(PayloadTypeOuterClass.PayloadType cmd, ICmdMsg<PayloadTypeOuterClass.PayloadType> cmdMsg) {
if (super.msgListeners.isEmpty()) {
return;
}
SocketMessageOuterClass.SocketMessage socketMessage = (SocketMessageOuterClass.SocketMessage) cmdMsg;
ByteString payloadByteString = socketMessage.getPayload();
switch (socketMessage.getPayloadType()) {
case SC_FEED_PUSH: {
SCWebFeedPushOuterClass.SCWebFeedPush scWebFeedPush = SCWebFeedPushOuterClass.SCWebFeedPush.parseFrom(payloadByteString);
if (scWebFeedPush.getCommentFeedsCount() > 0) {
for (WebCommentFeedOuterClass.WebCommentFeed webCommentFeed : scWebFeedPush.getCommentFeedsList()) {
iteratorMsgListeners(msgListener -> msgListener.onDanmuMsg(KuaishouBinaryFrameHandler.this, new KuaishouDanmuMsg(webCommentFeed)));
}
}
if (scWebFeedPush.getGiftFeedsCount() > 0) {
for (WebGiftFeedOuterClass.WebGiftFeed webGiftFeed : scWebFeedPush.getGiftFeedsList()) {
iteratorMsgListeners(msgListener -> msgListener.onGiftMsg(KuaishouBinaryFrameHandler.this, new KuaishouGiftMsg(webGiftFeed)));
}
}
if (scWebFeedPush.getLikeFeedsCount() > 0) {
for (WebLikeFeedOuterClass.WebLikeFeed webLikeFeed : scWebFeedPush.getLikeFeedsList()) {
iteratorMsgListeners(msgListener -> msgListener.onLikeMsg(KuaishouBinaryFrameHandler.this, new KuaishouLikeMsg(webLikeFeed)));
}
}
break;
}
default: {
iteratorMsgListeners(msgListener -> msgListener.onOtherCmdMsg(KuaishouBinaryFrameHandler.this, cmd, socketMessage));
}
}
}
@Override
protected List<IKuaishouMsg> decode(ByteBuf byteBuf) {
try {
SocketMessageOuterClass.SocketMessage socketMessage = SocketMessageOuterClass.SocketMessage.parseFrom(byteBuf.nioBuffer());
SocketMessageOuterClass.SocketMessage.CompressionType compressionType = socketMessage.getCompressionType();
ByteString payloadByteString = socketMessage.getPayload();
byte[] payload;
switch (compressionType) {
case NONE: {
payload = payloadByteString.toByteArray();
break;
}
case GZIP: {
payload = ZipUtil.unGzip(payloadByteString.newInput());
break;
}
default: {
if (log.isWarnEnabled()) {
log.warn("暂不支持的压缩方式 " + compressionType);
}
return Collections.emptyList();
}
}
return Collections.singletonList(socketMessage.toBuilder().setPayload(ByteString.copyFrom(payload)).build());
} catch (InvalidProtocolBufferException e) {
throw new BaseException(e);
}
}
}

View File

@@ -0,0 +1,158 @@
/*
* MIT License
*
* Copyright (c) 2023 OrdinaryRoad
*
* 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 tech.ordinaryroad.live.chat.client.kuaishou.netty.handler;
import cn.hutool.core.util.RandomUtil;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketClientHandshaker;
import lombok.extern.slf4j.Slf4j;
import tech.ordinaryroad.live.chat.client.commons.base.listener.IBaseConnectionListener;
import tech.ordinaryroad.live.chat.client.kuaishou.api.KuaishouApis;
import tech.ordinaryroad.live.chat.client.kuaishou.client.KuaishouLiveChatClient;
import tech.ordinaryroad.live.chat.client.kuaishou.config.KuaishouLiveChatClientConfig;
import tech.ordinaryroad.live.chat.client.kuaishou.protobuf.CSHeartbeatOuterClass;
import tech.ordinaryroad.live.chat.client.kuaishou.protobuf.CSWebEnterRoomOuterClass;
import tech.ordinaryroad.live.chat.client.kuaishou.protobuf.PayloadTypeOuterClass;
import tech.ordinaryroad.live.chat.client.kuaishou.protobuf.SocketMessageOuterClass;
import tech.ordinaryroad.live.chat.client.servers.netty.client.handler.BaseNettyClientConnectionHandler;
/**
* @author mjz
* @date 2024/1/5
*/
@Slf4j
@ChannelHandler.Sharable
public class KuaishouConnectionHandler extends BaseNettyClientConnectionHandler<KuaishouLiveChatClient, KuaishouConnectionHandler> {
/**
* 以ClientConfig为主
*/
private final Object roomId;
/**
* 以ClientConfig为主
*/
private String cookie;
private final KuaishouApis.RoomInitResult roomInitResult;
public KuaishouConnectionHandler(WebSocketClientHandshaker handshaker, KuaishouLiveChatClient client, IBaseConnectionListener<KuaishouConnectionHandler> listener) {
super(handshaker, client, listener);
this.roomId = client.getConfig().getRoomId();
this.cookie = client.getConfig().getCookie();
this.roomInitResult = client.getRoomInitResult();
}
public KuaishouConnectionHandler(WebSocketClientHandshaker handshaker, KuaishouLiveChatClient client) {
this(handshaker, client, null);
}
public KuaishouConnectionHandler(WebSocketClientHandshaker handshaker, long roomId, KuaishouApis.RoomInitResult roomInitResult, IBaseConnectionListener<KuaishouConnectionHandler> listener, String cookie) {
super(handshaker, listener);
this.roomId = roomId;
this.cookie = cookie;
this.roomInitResult = roomInitResult;
}
public KuaishouConnectionHandler(WebSocketClientHandshaker handshaker, long roomId, KuaishouApis.RoomInitResult roomInitResult, IBaseConnectionListener<KuaishouConnectionHandler> listener) {
this(handshaker, roomId, roomInitResult, listener, null);
}
public KuaishouConnectionHandler(WebSocketClientHandshaker handshaker, long roomId, KuaishouApis.RoomInitResult roomInitResult, String cookie) {
this(handshaker, roomId, roomInitResult, null, cookie);
}
public KuaishouConnectionHandler(WebSocketClientHandshaker handshaker, KuaishouApis.RoomInitResult roomInitResult, long roomId) {
this(handshaker, roomId, roomInitResult, null, null);
}
@Override
protected void sendHeartbeat(ChannelHandlerContext ctx) {
ctx.writeAndFlush(
new BinaryWebSocketFrame(
Unpooled.wrappedBuffer(SocketMessageOuterClass.SocketMessage.newBuilder()
.setPayloadType(PayloadTypeOuterClass.PayloadType.CS_HEARTBEAT)
.setPayload(
CSHeartbeatOuterClass.CSHeartbeat.newBuilder()
.setTimestamp(System.currentTimeMillis())
.build()
.toByteString()
)
.build()
.toByteArray()
)
)
);
}
@Override
public void sendAuthRequest(Channel channel) {
channel.writeAndFlush(
new BinaryWebSocketFrame(
Unpooled.wrappedBuffer(SocketMessageOuterClass.SocketMessage.newBuilder()
.setPayloadType(PayloadTypeOuterClass.PayloadType.CS_ENTER_ROOM)
.setPayload(
CSWebEnterRoomOuterClass.CSWebEnterRoom.newBuilder()
.setToken(roomInitResult.getToken())
.setLiveStreamId(roomInitResult.getLiveStreamId())
.setPageId(RandomUtil.randomString(16) + System.currentTimeMillis())
.build()
.toByteString()
)
.build()
.toByteArray()
)
)
);
}
@Override
protected long getHeartbeatPeriod() {
if (client == null) {
return KuaishouLiveChatClientConfig.DEFAULT_HEARTBEAT_PERIOD;
} else {
return client.getConfig().getHeartbeatPeriod();
}
}
@Override
protected long getHeartbeatInitialDelay() {
if (client == null) {
return KuaishouLiveChatClientConfig.DEFAULT_HEARTBEAT_INITIAL_DELAY;
} else {
return client.getConfig().getHeartbeatInitialDelay();
}
}
public Object getRoomId() {
return client != null ? client.getConfig().getRoomId() : roomId;
}
private String getCookie() {
return client != null ? client.getConfig().getCookie() : cookie;
}
}

View File

@@ -0,0 +1,216 @@
/*
* MIT License
*
* Copyright (c) 2023 OrdinaryRoad
*
* 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.
*/
// Generated by the protocol buffer compiler. DO NOT EDIT!
// source: ClientId.proto
package tech.ordinaryroad.live.chat.client.kuaishou.protobuf;
public final class ClientIdOuterClass {
private ClientIdOuterClass() {}
public static void registerAllExtensions(
com.google.protobuf.ExtensionRegistryLite registry) {
}
public static void registerAllExtensions(
com.google.protobuf.ExtensionRegistry registry) {
registerAllExtensions(
(com.google.protobuf.ExtensionRegistryLite) registry);
}
/**
* Protobuf enum {@code ClientId}
*/
public enum ClientId
implements com.google.protobuf.ProtocolMessageEnum {
/**
* <code>NONE = 0;</code>
*/
NONE(0),
/**
* <code>IPHONE = 1;</code>
*/
IPHONE(1),
/**
* <code>ANDROID = 2;</code>
*/
ANDROID(2),
/**
* <code>WEB = 3;</code>
*/
WEB(3),
/**
* <code>PC = 6;</code>
*/
PC(6),
/**
* <code>IPHONE_LIVE_MATE = 8;</code>
*/
IPHONE_LIVE_MATE(8),
/**
* <code>ANDROID_LIVE_MATE = 9;</code>
*/
ANDROID_LIVE_MATE(9),
UNRECOGNIZED(-1),
;
/**
* <code>NONE = 0;</code>
*/
public static final int NONE_VALUE = 0;
/**
* <code>IPHONE = 1;</code>
*/
public static final int IPHONE_VALUE = 1;
/**
* <code>ANDROID = 2;</code>
*/
public static final int ANDROID_VALUE = 2;
/**
* <code>WEB = 3;</code>
*/
public static final int WEB_VALUE = 3;
/**
* <code>PC = 6;</code>
*/
public static final int PC_VALUE = 6;
/**
* <code>IPHONE_LIVE_MATE = 8;</code>
*/
public static final int IPHONE_LIVE_MATE_VALUE = 8;
/**
* <code>ANDROID_LIVE_MATE = 9;</code>
*/
public static final int ANDROID_LIVE_MATE_VALUE = 9;
public final int getNumber() {
if (this == UNRECOGNIZED) {
throw new java.lang.IllegalArgumentException(
"Can't get the number of an unknown enum value.");
}
return value;
}
/**
* @param value The numeric wire value of the corresponding enum entry.
* @return The enum associated with the given numeric wire value.
* @deprecated Use {@link #forNumber(int)} instead.
*/
@java.lang.Deprecated
public static ClientId valueOf(int value) {
return forNumber(value);
}
/**
* @param value The numeric wire value of the corresponding enum entry.
* @return The enum associated with the given numeric wire value.
*/
public static ClientId forNumber(int value) {
switch (value) {
case 0: return NONE;
case 1: return IPHONE;
case 2: return ANDROID;
case 3: return WEB;
case 6: return PC;
case 8: return IPHONE_LIVE_MATE;
case 9: return ANDROID_LIVE_MATE;
default: return null;
}
}
public static com.google.protobuf.Internal.EnumLiteMap<ClientId>
internalGetValueMap() {
return internalValueMap;
}
private static final com.google.protobuf.Internal.EnumLiteMap<
ClientId> internalValueMap =
new com.google.protobuf.Internal.EnumLiteMap<ClientId>() {
public ClientId findValueByNumber(int number) {
return ClientId.forNumber(number);
}
};
public final com.google.protobuf.Descriptors.EnumValueDescriptor
getValueDescriptor() {
if (this == UNRECOGNIZED) {
throw new java.lang.IllegalStateException(
"Can't get the descriptor of an unrecognized enum value.");
}
return getDescriptor().getValues().get(ordinal());
}
public final com.google.protobuf.Descriptors.EnumDescriptor
getDescriptorForType() {
return getDescriptor();
}
public static final com.google.protobuf.Descriptors.EnumDescriptor
getDescriptor() {
return tech.ordinaryroad.live.chat.client.kuaishou.protobuf.ClientIdOuterClass.getDescriptor().getEnumTypes().get(0);
}
private static final ClientId[] VALUES = values();
public static ClientId valueOf(
com.google.protobuf.Descriptors.EnumValueDescriptor desc) {
if (desc.getType() != getDescriptor()) {
throw new java.lang.IllegalArgumentException(
"EnumValueDescriptor is not for this type.");
}
if (desc.getIndex() == -1) {
return UNRECOGNIZED;
}
return VALUES[desc.getIndex()];
}
private final int value;
private ClientId(int value) {
this.value = value;
}
// @@protoc_insertion_point(enum_scope:ClientId)
}
public static com.google.protobuf.Descriptors.FileDescriptor
getDescriptor() {
return descriptor;
}
private static com.google.protobuf.Descriptors.FileDescriptor
descriptor;
static {
java.lang.String[] descriptorData = {
"\n\016ClientId.proto*k\n\010ClientId\022\010\n\004NONE\020\000\022\n" +
"\n\006IPHONE\020\001\022\013\n\007ANDROID\020\002\022\007\n\003WEB\020\003\022\006\n\002PC\020\006" +
"\022\024\n\020IPHONE_LIVE_MATE\020\010\022\025\n\021ANDROID_LIVE_M" +
"ATE\020\tB6\n4tech.ordinaryroad.live.chat.cli" +
"ent.kuaishou.protobufb\006proto3"
};
descriptor = com.google.protobuf.Descriptors.FileDescriptor
.internalBuildGeneratedFileFrom(descriptorData,
new com.google.protobuf.Descriptors.FileDescriptor[] {
});
}
// @@protoc_insertion_point(outer_class_scope)
}

View File

@@ -0,0 +1,180 @@
/*
* MIT License
*
* Copyright (c) 2023 OrdinaryRoad
*
* 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.
*/
// Generated by the protocol buffer compiler. DO NOT EDIT!
// source: ConfigSwitchType.proto
package tech.ordinaryroad.live.chat.client.kuaishou.protobuf;
public final class ConfigSwitchTypeOuterClass {
private ConfigSwitchTypeOuterClass() {}
public static void registerAllExtensions(
com.google.protobuf.ExtensionRegistryLite registry) {
}
public static void registerAllExtensions(
com.google.protobuf.ExtensionRegistry registry) {
registerAllExtensions(
(com.google.protobuf.ExtensionRegistryLite) registry);
}
/**
* Protobuf enum {@code ConfigSwitchType}
*/
public enum ConfigSwitchType
implements com.google.protobuf.ProtocolMessageEnum {
/**
* <code>UNKNOWN = 0;</code>
*/
UNKNOWN(0),
/**
* <code>HIDE_BARRAGE = 1;</code>
*/
HIDE_BARRAGE(1),
/**
* <code>HIDE_SPECIAL_EFFECT = 2;</code>
*/
HIDE_SPECIAL_EFFECT(2),
UNRECOGNIZED(-1),
;
/**
* <code>UNKNOWN = 0;</code>
*/
public static final int UNKNOWN_VALUE = 0;
/**
* <code>HIDE_BARRAGE = 1;</code>
*/
public static final int HIDE_BARRAGE_VALUE = 1;
/**
* <code>HIDE_SPECIAL_EFFECT = 2;</code>
*/
public static final int HIDE_SPECIAL_EFFECT_VALUE = 2;
public final int getNumber() {
if (this == UNRECOGNIZED) {
throw new java.lang.IllegalArgumentException(
"Can't get the number of an unknown enum value.");
}
return value;
}
/**
* @param value The numeric wire value of the corresponding enum entry.
* @return The enum associated with the given numeric wire value.
* @deprecated Use {@link #forNumber(int)} instead.
*/
@java.lang.Deprecated
public static ConfigSwitchType valueOf(int value) {
return forNumber(value);
}
/**
* @param value The numeric wire value of the corresponding enum entry.
* @return The enum associated with the given numeric wire value.
*/
public static ConfigSwitchType forNumber(int value) {
switch (value) {
case 0: return UNKNOWN;
case 1: return HIDE_BARRAGE;
case 2: return HIDE_SPECIAL_EFFECT;
default: return null;
}
}
public static com.google.protobuf.Internal.EnumLiteMap<ConfigSwitchType>
internalGetValueMap() {
return internalValueMap;
}
private static final com.google.protobuf.Internal.EnumLiteMap<
ConfigSwitchType> internalValueMap =
new com.google.protobuf.Internal.EnumLiteMap<ConfigSwitchType>() {
public ConfigSwitchType findValueByNumber(int number) {
return ConfigSwitchType.forNumber(number);
}
};
public final com.google.protobuf.Descriptors.EnumValueDescriptor
getValueDescriptor() {
if (this == UNRECOGNIZED) {
throw new java.lang.IllegalStateException(
"Can't get the descriptor of an unrecognized enum value.");
}
return getDescriptor().getValues().get(ordinal());
}
public final com.google.protobuf.Descriptors.EnumDescriptor
getDescriptorForType() {
return getDescriptor();
}
public static final com.google.protobuf.Descriptors.EnumDescriptor
getDescriptor() {
return tech.ordinaryroad.live.chat.client.kuaishou.protobuf.ConfigSwitchTypeOuterClass.getDescriptor().getEnumTypes().get(0);
}
private static final ConfigSwitchType[] VALUES = values();
public static ConfigSwitchType valueOf(
com.google.protobuf.Descriptors.EnumValueDescriptor desc) {
if (desc.getType() != getDescriptor()) {
throw new java.lang.IllegalArgumentException(
"EnumValueDescriptor is not for this type.");
}
if (desc.getIndex() == -1) {
return UNRECOGNIZED;
}
return VALUES[desc.getIndex()];
}
private final int value;
private ConfigSwitchType(int value) {
this.value = value;
}
// @@protoc_insertion_point(enum_scope:ConfigSwitchType)
}
public static com.google.protobuf.Descriptors.FileDescriptor
getDescriptor() {
return descriptor;
}
private static com.google.protobuf.Descriptors.FileDescriptor
descriptor;
static {
java.lang.String[] descriptorData = {
"\n\026ConfigSwitchType.proto*J\n\020ConfigSwitch" +
"Type\022\013\n\007UNKNOWN\020\000\022\020\n\014HIDE_BARRAGE\020\001\022\027\n\023H" +
"IDE_SPECIAL_EFFECT\020\002B6\n4tech.ordinaryroa" +
"d.live.chat.client.kuaishou.protobufb\006pr" +
"oto3"
};
descriptor = com.google.protobuf.Descriptors.FileDescriptor
.internalBuildGeneratedFileFrom(descriptorData,
new com.google.protobuf.Descriptors.FileDescriptor[] {
});
}
// @@protoc_insertion_point(outer_class_scope)
}

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