Commit 325fad04 by zhiwei

修改B站、斗鱼、熊猫TV弹幕数据返回形式,及修复获取弹幕时不发心跳的问题

parent 01161ad1
...@@ -12,6 +12,7 @@ import com.zhiwei.crawler.core.HttpBoot; ...@@ -12,6 +12,7 @@ import com.zhiwei.crawler.core.HttpBoot;
import com.zhiwei.crawler.core.RequestUtils; import com.zhiwei.crawler.core.RequestUtils;
import com.zhiwei.live.bean.RoomInfo; import com.zhiwei.live.bean.RoomInfo;
import com.zhiwei.live.danmu.util.Connector; import com.zhiwei.live.danmu.util.Connector;
import com.zhiwei.live.danmu.util.DataCallBack;
import com.zhiwei.live.roominfo.BilibiliRoomInfoCrawler; import com.zhiwei.live.roominfo.BilibiliRoomInfoCrawler;
import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup;
...@@ -25,27 +26,6 @@ public class BilibiliClient { ...@@ -25,27 +26,6 @@ public class BilibiliClient {
private static Logger logger = LogManager.getLogger(BilibiliClient.class); private static Logger logger = LogManager.getLogger(BilibiliClient.class);
private static final int PORT = 2243; private static final int PORT = 2243;
public static void main(String[] args) throws InterruptedException {
String roomUrl = "https://live.bilibili.com/139";
String roomId = roomUrl.replaceAll("https://live.bilibili.com/", "");
try {
getDanmu(new DataCallBack() {
@Override
public void onData(BilibiliMessage data) {
System.out.println("-------------" + data.toString());
}
},roomId);
} catch (Exception e) {
e.printStackTrace();
}
}
/** /**
* 根据房间号获取弹幕信息 * 根据房间号获取弹幕信息
* @param roomId * @param roomId
......
package com.zhiwei.live.danmu.bilibili; package com.zhiwei.live.danmu.bilibili;
import static java.util.Objects.requireNonNull;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import com.zhiwei.live.danmu.util.DataCallBack;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil; import io.netty.buffer.ByteBufUtil;
...@@ -22,7 +25,7 @@ public class BilibiliMessageHandler extends ChannelInboundHandlerAdapter{ ...@@ -22,7 +25,7 @@ public class BilibiliMessageHandler extends ChannelInboundHandlerAdapter{
private static final byte[] BILI_IN = new byte[] { 0x00, 0x10, 0x00 , 0x01, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,0x01}; private static final byte[] BILI_IN = new byte[] { 0x00, 0x10, 0x00 , 0x01, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,0x01};
private static final byte[] PING = new byte[] { 0x00, 0x00, 0x00, 0x1F, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x5B, 0x6F, 0x62, 0x6A, 0x65, 0x63, 0x74, 0x20, 0x4F, 0x62, 0x6A, 0x65, 0x63, 0x74, 0x5D }; private static final byte[] PING = new byte[] { 0x00, 0x00, 0x00, 0x1F, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x5B, 0x6F, 0x62, 0x6A, 0x65, 0x63, 0x74, 0x20, 0x4F, 0x62, 0x6A, 0x65, 0x63, 0x74, 0x5D };
private static Pattern pattern = Pattern.compile("\\{\"cmd\":\"DANMU_MSG.+?\\]\\}"); private static Pattern pattern = Pattern.compile("\\{\"cmd\":\"DANMU_MSG.+?\\]\\}");
private String roomid; private String roomId;
private DataCallBack dataCallBack; private DataCallBack dataCallBack;
/** /**
...@@ -33,7 +36,7 @@ public class BilibiliMessageHandler extends ChannelInboundHandlerAdapter{ ...@@ -33,7 +36,7 @@ public class BilibiliMessageHandler extends ChannelInboundHandlerAdapter{
*/ */
public BilibiliMessageHandler(DataCallBack dataCallBack,String roomid) { public BilibiliMessageHandler(DataCallBack dataCallBack,String roomid) {
this.dataCallBack = dataCallBack; this.dataCallBack = dataCallBack;
this.roomid = roomid; this.roomId = requireNonNull(roomid, "roomId is null");
} }
/* /*
...@@ -45,7 +48,7 @@ public class BilibiliMessageHandler extends ChannelInboundHandlerAdapter{ ...@@ -45,7 +48,7 @@ public class BilibiliMessageHandler extends ChannelInboundHandlerAdapter{
public void channelActive(ChannelHandlerContext ctx) throws Exception { public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("TCP 连接建立成功: " + ctx.channel()); System.out.println("TCP 连接建立成功: " + ctx.channel());
byte[] body = StringUtils byte[] body = StringUtils
.join("{\"uid\":0,\"roomid\":", roomid, ",\"protover\":1,\"platform\":\"web\",\"clientver\":\"1.5.15\"}") .join("{\"uid\":0,\"roomid\":", roomId, ",\"protover\":1,\"platform\":\"web\",\"clientver\":\"1.5.15\"}")
.getBytes(); .getBytes();
ByteBuf loginMsg = Unpooled.wrappedBuffer(Unpooled.wrappedBuffer(FIRST_REQ) ByteBuf loginMsg = Unpooled.wrappedBuffer(Unpooled.wrappedBuffer(FIRST_REQ)
,Unpooled.buffer(1).writeByte(body.length+16),Unpooled.wrappedBuffer(BILI_IN), Unpooled.wrappedBuffer(body)); ,Unpooled.buffer(1).writeByte(body.length+16),Unpooled.wrappedBuffer(BILI_IN), Unpooled.wrappedBuffer(body));
......
package com.zhiwei.live.danmu.bilibili;
import java.util.List;
import java.util.Vector;
public class BilibiliMessageListener {
public static List<BilibiliMessage> messages = new Vector<>();
public static void addMessages(BilibiliMessage bilibiliMessage) {
messages.add(bilibiliMessage);
}
}
package com.zhiwei.live.danmu.douyu;
/**
*
* 功能描述:斗鱼弹幕服务API
* 基于(斗鱼弹幕服务器第三方接入协议v1.6.2)开发
*
* @auther: coffee
* @date: 2018-07-04 15:45:56
* 修改日志:
*
*/
public class DouYuApi {
public static String LOGIN_REQ = "type@=loginreq/roomid@=%s/";
public static String JOIN_GROUP = "type@=joingroup/rid@=%s/gid@=%s/";
public static String KEEP_LIVE = "type@=mrkl/";
public static String LOGOUT = "type@=logout/";
}
package com.zhiwei.live.danmu.douyu;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.zhiwei.crawler.proxy.ProxyHolder;
import com.zhiwei.live.danmu.douyu.constants.DouYuConstants;
import com.zhiwei.live.danmu.douyu.constants.MsgType;
import com.zhiwei.live.danmu.douyu.entity.BaseMsg;
import com.zhiwei.live.danmu.douyu.entity.ChatMsg;
import com.zhiwei.live.danmu.douyu.entity.DgbMsg;
import com.zhiwei.live.danmu.douyu.entity.ErrorMsg;
import com.zhiwei.live.danmu.douyu.entity.GgbbMsg;
import com.zhiwei.live.danmu.douyu.entity.MsgEntity;
import com.zhiwei.live.danmu.douyu.entity.SpbcMsg;
import com.zhiwei.live.danmu.douyu.entity.SsdMsg;
import com.zhiwei.live.danmu.douyu.entity.UenterMsg;
import com.zhiwei.live.danmu.douyu.exceptions.DouYuSDKException;
import com.zhiwei.live.danmu.douyu.util.DataUtil;
import com.zhiwei.live.danmu.douyu.util.STTUtil;
import com.zhiwei.live.danmu.douyu.util.UUIDUtil;
/**
* 功能描述:斗鱼SDK
*
* @auther: coffee
* @date: 2018-07-04 15:19:51
* 修改日志:
*/
public class DouYuClient {
private final static Logger logger = LoggerFactory.getLogger(DouYuClient.class);
private String host; //API Host
private int port; //API 端口
private String roomId; //房间ID(房间号)
private String groupId = DouYuConstants.MASSIVE_GID; //分组ID
private Socket socket; //通讯socket对象
private Thread dataSyncThread; //异步读取消息线程
private Boolean isExitMark = false; //退出标记
private MessageHandler messageHandler;
private List<MessageListener> messageListenerList = new ArrayList<>();
public DouYuClient(String host, int port, String roomId) {
this.host = host;
this.port = port;
this.roomId = roomId;
//初始化
this.init();
}
public DouYuClient(String roomId) {
this.host = "openbarrage.douyutv.com";
this.port = 8601;
this.roomId = roomId;
//初始化
this.init();
}
/**
* 初始化Client
*/
private void init() {
logger.info("初始化斗鱼SDK_Client...");
this.connect();
}
/**
* 打开socket连接
*/
private void connect() {
try {
logger.info("从服务器({}:{})获取弹幕服务器数据", host, port);
socket = new Socket(ProxyHolder.NAT_PROXY.getProxy());
socket.connect(new InetSocketAddress(host, port));
messageHandler = new MessageHandler(socket);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* 登入斗鱼弹幕服务器
*/
public DouYuClient login() {
logger.info("登录房间 {}", roomId);
String content = String.format(DouYuApi.LOGIN_REQ, roomId);
messageHandler.send(content);
return this;
}
/**
* 加入房间分组并接收房间消息
*
* @return
*/
private DouYuClient joinGroup(String groupId) {
if (DouYuConstants.MASSIVE_GID.equals(groupId)) {
logger.info("开启海量弹幕接收模式");
} else {
logger.info("关闭海量弹幕接收模式");
}
logger.info("加入分组({})并开始接收消息", groupId);
String content = String.format(DouYuApi.JOIN_GROUP, roomId, groupId);
messageHandler.send(content);
return this;
}
/**
* 注册消息监听器
*
* @param messageListener
* @return
*/
public DouYuClient registerMessageListener(MessageListener messageListener) {
logger.info("注册消息监听器:{}", messageListener.getClass());
this.messageListenerList.add(messageListener);
return this;
}
/**
* 开始同步并接收房间消息
*/
public DouYuClient sync() {
//加入海量弹幕分组,接收所有消息
this.joinGroup(groupId);
//开启异步线程接收房间消息
dataSyncThread = new Thread(new Runnable() {
@Override
public void run() {
long start = System.currentTimeMillis();
while (true) {
//判断是否退出线程
if (isExitMark == true) {
break;
}
//读取弹幕消息
byte[] bytes = messageHandler.read();
String msg = new String(Arrays.copyOfRange(bytes, 8, bytes.length));
//获取消息类型
String msgType = DataUtil.getMsgType(msg);
if (msgType == null) {
logger.error("获取消息类型失败,消息:{}", msg);
continue;
}
//封装基础消息对象
BaseMsg msgBase = new BaseMsg();
msgBase.setUuid(UUIDUtil.simpleUUID());
msgBase.setType(msgType);
msgBase.setMessage(msg);
//根据不同的消息类型 序列化不同的 实体对象
MsgEntity entity = null;
if (MsgType.CHAT_MSG.equals(msgType)) {
entity = STTUtil.toBean(msg, ChatMsg.class);
} else if (MsgType.DGB.equals(msgType)) {
entity = STTUtil.toBean(msg, DgbMsg.class);
} else if (MsgType.GGBB.equals(msgType)) {
entity = STTUtil.toBean(msg, GgbbMsg.class);
} else if (MsgType.SPBC.equals(msgType)) {
entity = STTUtil.toBean(msg, SpbcMsg.class);
} else if (MsgType.SSD.equals(msgType)) {
entity = STTUtil.toBean(msg, SsdMsg.class);
} else if (MsgType.UENTER.equals(msgType)) {
entity = STTUtil.toBean(msg, UenterMsg.class);
} else if (MsgType.ERROR.equals(msgType)) {
entity = STTUtil.toBean(msg, ErrorMsg.class);
}
if (entity != null) {
entity.setMessage(msg);
entity.setUuid(msgBase.getUuid());
}
//消息监听器处理
for (MessageListener messageListener : messageListenerList) {
try {
//基础消息监听器处理
if (messageListener.getMsgClazz() == BaseMsg.class) {
messageListener.read(msgBase);
}
//指定类型消息监听器处理
else if (entity != null && messageListener.getMsgClazz() == entity.getClass()) {
messageListener.read(entity);
}
//String消息监听器处理
else if (messageListener.getMsgClazz() == String.class) {
messageListener.read(msg);
}
} catch (Exception e) {
logger.error("消息处理出现异常:", e);
}
}
//发送心跳消息保持通道
long end = System.currentTimeMillis();
if (end - start > 30000) {
doKeepLive();
start = System.currentTimeMillis();
}
//休眠1毫秒
try {
Thread.sleep(1);
} catch (InterruptedException e) {
throw new DouYuSDKException(e);
}
}
//客户端关闭,断开socket通道
messageHandler.close();
logger.info("斗鱼弹幕SDK客户端已成功退出");
}
});
dataSyncThread.start();
return this;
}
/**
* 发送心跳消息,保持通道
*/
public void doKeepLive() {
String content = String.format(DouYuApi.KEEP_LIVE);
logger.info("发送心跳信息,保持通道中...");
messageHandler.send(content);
}
/**
* 发送登出消息,用于退出
*/
public void logout(){
String content = String.format(DouYuApi.LOGOUT);
logger.info("发送登出消息中...");
messageHandler.send(content);
}
public void exit() {
logout();
isExitMark = true;
logger.info("斗鱼弹幕SDK客户端正在退出中...");
}
}
package com.zhiwei.live.danmu.douyu;
import java.util.Date;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
public class DouYuMessage {
String messageType; //弹幕消息类型
String user_id; //发布者uid
String nickName; //发布者昵称
Date time; //弹幕时间
String content; //弹幕内容
String room_id; //房间id
public DouYuMessage(JSONObject json) throws Exception {
constructJson(json);
}
public DouYuMessage(){
}
private void constructJson(JSONObject json) throws Exception{
try {
user_id = json.getString("uid");
time = new Date();
nickName = json.getString("nn");
content = json.getString("txt");
room_id = json.getString("rid");
} catch (Exception e) {
throw new Exception();
}
}
@Override
public String toString() {
return "new BilibiliMessage["
+ "user_id = " + user_id
+ ", nickName = " + nickName
+ ", time = " + time
+ ", content = " + content
+ ", room_id = " + room_id
+ "]";
}
public Date getTime() {
return time;
}
public void setTime(Date time) {
this.time = time;
}
public String getMessageType() {
return messageType;
}
public String getUser_id() {
return user_id;
}
public String getNickName() {
return nickName;
}
public String getContent() {
return content;
}
public void setMessageType(String messageType) {
this.messageType = messageType;
}
public void setUser_id(String user_id) {
this.user_id = user_id;
}
public void setNickName(String nickName) {
this.nickName = nickName;
}
public void setContent(String content) {
this.content = content;
}
public String getRoom_id() {
return room_id;
}
public void setRoom_id(String room_id) {
this.room_id = room_id;
}
}
package com.zhiwei.live.danmu.douyu;
import java.util.Objects;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.zhiwei.live.bean.RoomInfo;
import com.zhiwei.live.danmu.util.Connector;
import com.zhiwei.live.danmu.util.DataCallBack;
import com.zhiwei.live.roominfo.DouYuRoomInfoCrawler;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.handler.timeout.IdleStateHandler;
/**
* @ClassName: DouyuClient
* @Description: TODO
* @author 0xff
* @date Jan 25, 2019 10:57:49 AM
*/
public class DouyuClient {
private static Logger logger = LogManager.getLogger(DouyuClient.class);
private static final int PORT = 8601;
private static final String HOST = "openbarrage.douyutv.com";
/**
* 根据房间号获取弹幕信息
*
* @param roomId
* @throws Exception
*/
public static void getDanmu(DataCallBack dataCallBack, String roomId) throws Exception {
// 根据房间号获取真实房间号
RoomInfo roomInfo = DouYuRoomInfoCrawler.getRoomInfoByRoomId(roomId);
if (Objects.nonNull(roomInfo)) {
// 建立弹幕连接
Connector.asynchronizedTcpConnect(new NioEventLoopGroup(), HOST, PORT, new IdleStateHandler(0, 30, 45),
new DouyuMessageHandler(dataCallBack, roomInfo.getRoomId())).sync();
} else {
logger.info("获取真实房间号出现问题,请及时检查程序");
}
}
}
package com.zhiwei.live.danmu.douyu;
import static java.util.Objects.requireNonNull;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import com.alibaba.fastjson.JSONObject;
import com.zhiwei.live.danmu.util.DataCallBack;
import com.zhiwei.live.danmu.util.DouYuUtil;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.util.CharsetUtil;
import io.netty.util.ReferenceCountUtil;
/**
* @ClassName: WebSocketMessageHandler
* @Description: TODO
* @author 0xff
* @date Jan 25, 2019 11:03:20 AM
*/
public class DouyuMessageHandler extends ChannelInboundHandlerAdapter {
private String roomId;
private boolean finish;
private DataCallBack dataCallBack;
/**
* Constructor
*
* @param roomId
* 房间号
*/
public DouyuMessageHandler(DataCallBack dataCallBack,String roomid) {
this.dataCallBack = dataCallBack;
this.roomId = requireNonNull(roomId, "roomId is null");
}
/*
* @see
* io.netty.channel.ChannelInboundHandlerAdapter#channelActive(io.netty.channel.
* ChannelHandlerContext)
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("TCP 连接建立成功: " + ctx.channel());
Channel channel = ctx.channel();
ByteBuf loginMsg = buildMsg(StringUtils.join("type@=loginreq/roomid@=", roomId, "/"));
System.out.println("发送登录消息: \n" + ByteBufUtil.prettyHexDump(loginMsg));
channel.writeAndFlush(loginMsg).addListener((ChannelFutureListener) future -> {
if(!future.isSuccess()) {
System.out.println("发送登录消息失败");
}
});
}
/*
* @see io.netty.channel.ChannelInboundHandlerAdapter#channelRead(io.netty.channel.ChannelHandlerContext, java.lang.Object)
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg instanceof ByteBuf) {
ByteBuf buf = (ByteBuf) msg;
String source = buf.toString(CharsetUtil.UTF_8);
// System.out.println("source========="+source);
if(source.contains("chatmsg")) {
Map<String,Object> messageMap = DouYuUtil.toMap(source);
String data = JSONObject.toJSONString(messageMap);
JSONObject messagesJson = JSONObject.parseObject(data);
DouYuMessage douYuMessage = new DouYuMessage(messagesJson);
System.out.println(douYuMessage.getContent());
}
}
ReferenceCountUtil.release(msg);
if(!finish) {
ByteBuf groupMsg = buildMsg(StringUtils.join("type@=joingroup/rid@=",roomId,"/gid@=-9999/"));
System.out.println("发送入组消息: \n" + ByteBufUtil.prettyHexDump(groupMsg));
ctx.writeAndFlush(groupMsg);
finish = true;
}
ctx.fireChannelRead(msg);
}
/*
* @see io.netty.channel.ChannelInboundHandlerAdapter#userEventTriggered(io.netty.channel.ChannelHandlerContext, java.lang.Object)
*/
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if (evt instanceof IdleStateEvent) {
IdleStateEvent event = (IdleStateEvent) evt;
if (event.state() == IdleState.WRITER_IDLE) {
ByteBuf pingMsg = buildMsg(StringUtils.join("type@=keeplive/tick@=", System.currentTimeMillis() / 1000, "/"));
System.out.println("发送心跳消息: \n" + ByteBufUtil.prettyHexDump(pingMsg));
ctx.writeAndFlush(pingMsg);
} else {
ctx.fireUserEventTriggered(evt);
}
} else {
ctx.fireUserEventTriggered(evt);
}
}
private ByteBuf buildMsg(String content) {
int fixedLen = 4 + 4 + 1;
byte[] body = content.getBytes(CharsetUtil.UTF_8);
int length = fixedLen + body.length;
return Unpooled.buffer(length).writeIntLE(length).writeIntLE(length).writeShortLE(689)
.writeShortLE(0).writeBytes(body).writeByte('\0');
}
}
package com.zhiwei.live.danmu.douyu;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.zhiwei.common.config.GroupType;
import com.zhiwei.crawler.proxy.ProxyFactory;
import com.zhiwei.live.danmu.douyu.entity.ChatMsg;
/**
* 功能描述:
*
* @auther: coffee
* @date: 2018-07-04 19:37:41
* 修改日志:
*/
public class Main {
private final static Logger logger = LoggerFactory.getLogger(Main.class);
private static final String registry = "zookeeper://192.168.0.36:2181";
private static final String group = "local";
static {
ProxyFactory.init(registry, group, GroupType.PROVIDER);
}
public static void main(String[] args) throws InterruptedException {
DouYuClient client = new DouYuClient("3084150");
client.registerMessageListener(new MessageListener<ChatMsg>() {
@Override
public void read(ChatMsg message) {
//logger.info(message.toChatStr());
// System.out.println(message.toChatStr());
}
});
client.registerMessageListener(new MessageListener<String>() {
@Override
public void read(String message) {
System.out.println(message);
// logger.info(message);
}
});
client.login();
client.sync();
Thread.sleep(30000);
client.exit();
}
}
package com.zhiwei.live.danmu.douyu;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Arrays;
/**
*
* 功能描述:斗鱼消息实体
*
* @auther: coffee
* @date: 2018-07-04 15:06:45
* 修改日志:
*
*/
public class Message {
/**
* 请求消息体包含五部分:
* 1.计算后四部分的字节长度,占4个字节
* 2.内容同上
* 3.请求代码,固定,发到斗鱼是0xb1,0x02,0x00,0x00,接收是0xb2,0x02,0x00,0x00,4个字节
* 4.消息正文
* 5.尾部1个空字节
*/
private int[] length1;
private int[] length2;
private int[] magic;
private String content;
private int[] end;
public Message(String content) {
length1 = new int[]{calcMessageLength(content), 0x00, 0x00, 0x00};
length2 = new int[]{calcMessageLength(content), 0x00, 0x00, 0x00};
magic = new int[]{0xb1, 0x02, 0x00, 0x00};
this.content = content;
end = new int[]{0x00};
}
/**
* 计算消息体长度
*/
private int calcMessageLength(String content) {
return 4 + 4 + (content == null ? 0 : content.length()) + 1;
}
@Override
public String toString() {
return "Message{" +
"length1=" + Arrays.toString(length1) +
", length2=" + Arrays.toString(length2) +
", magic=" + Arrays.toString(magic) +
", content='" + content + '\'' +
", end=" + Arrays.toString(end) +
'}';
}
/**
* 将Message对象转化为字节数组
*/
public byte[] getBytes() throws IOException {
ByteArrayOutputStream byteArray = new ByteArrayOutputStream();
//写入长度1
for(int b : length1){
byteArray.write(b);
}
//写入长度2
for(int b : length2){
byteArray.write(b);
}
//写入消息类型
for(int b : magic){
byteArray.write(b);
}
//写入消息内容
if(content != null){
byteArray.write(content.getBytes("ISO-8859-1"));
}
//写入尾部结束符
for(int b : end){
byteArray.write(b);
}
return byteArray.toByteArray();
}
}
package com.zhiwei.live.danmu.douyu;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.zhiwei.live.danmu.douyu.exceptions.DouYuSDKException;
/**
* 功能描述:消息处理助手
*
* @auther: coffee
* @date: 2018-07-04 15:11:13
* 修改日志:
*/
public class MessageHandler {
private final static Logger logger = LoggerFactory.getLogger(MessageHandler.class);
private Socket socket;
public MessageHandler(Socket socket) {
this.socket = socket;
}
/**
* 发送消息
*
* @param content
*/
public void send(String content) {
try {
Message message = new Message(content);
OutputStream out = socket.getOutputStream();
out.write(message.getBytes());
out.flush();
} catch (IOException e) {
throw new DouYuSDKException(e);
}
}
/**
* 读取消息
*
* @return
*/
public byte[] read(){
try {
InputStream inputStream = socket.getInputStream();
//下条信息的长度
int contentLen = 0;
//读取前4个字节,得到数据长度
for (int i = 0; i < 4; i++) {
int tmp = inputStream.read();
contentLen += tmp * Math.pow(16, 2 * i);
}
int len = 0;
int readLen = 0;
byte[] bytes = new byte[contentLen];
ByteArrayOutputStream byteArray = new ByteArrayOutputStream();
while ((len = inputStream.read(bytes, 0, contentLen - readLen)) != -1) {
byteArray.write(bytes, 0, len);
readLen += len;
if (readLen == contentLen) {
break;
}
}
return byteArray.toByteArray();
} catch (IOException e) {
throw new DouYuSDKException(e);
}
}
/**
* 关闭socket通道
*
* @throws IOException
*/
public void close(){
try {
socket.close();
} catch (IOException e) {
logger.warn("socket通道关闭异常",e);
}
}
}
package com.zhiwei.live.danmu.douyu;
import java.lang.reflect.ParameterizedType;
/**
* 功能描述:消息监听器
*
* @auther: coffee
* @date: 2018-07-04 16:22:22
* 修改日志:
*/
public abstract class MessageListener<T> {
private Class<T> msgClazz;
public MessageListener() {
ParameterizedType type = (ParameterizedType) this.getClass().getGenericSuperclass();
this.msgClazz = (Class<T>) type.getActualTypeArguments()[0];
}
/**
* 消息读取
*/
public abstract void read(T message);
public Class<T> getMsgClazz() {
return msgClazz;
}
}
package com.zhiwei.live.danmu.douyu.constants;
/**
*
* 功能描述:斗鱼常量
*
* @auther: coffee
* @date: 2018-07-06 11:27:32
* 修改日志:
*
*/
public class DouYuConstants {
//海量弹幕分组
public final static String MASSIVE_GID = "-9999";
}
package com.zhiwei.live.danmu.douyu.constants;
/**
*
* 功能描述:消息类型
*
* @auther: coffee
* @date: 2018-07-07 16:22:36
* 修改日志:
*
*/
public class MsgType {
//登录响应
public static final String LOGIN_RES = "loginres";
//服务心跳响应
public static final String KEEP_LIVE = "loginres";
//弹幕消息
public static final String CHAT_MSG = "chatmsg";
//领取鱼丸暴击
public static final String ONLINE_GIFT = "onlinegift";
//赠送礼物消息
public static final String DGB = "dgb";
//用户进入房间
public static final String UENTER = "uenter";
//房间开播关播
public static final String RSS = "rss";
//房间贡献排行榜更新广播
public static final String RANK_LIST = "ranklist";
//超级弹幕
public static final String SSD = "ssd";
//房间内礼物广播
public static final String SPBC = "spbc";
//房间用户抢红包
public static final String GGBB = "ggbb";
//房间分区排名变化消息
public static final String RANK_UP = "rankup";
//错误信息
public static final String ERROR = "error";
}
package com.zhiwei.live.danmu.douyu.entity;
/**
* 功能描述:通用消息实体
*
* @auther: coffee
* @date: 2018-07-07 17:16:27
* 修改日志:
*/
public class BaseMsg implements MsgEntity{
private String uuid;
private String type;
private String message;
public String getUuid() {
return uuid;
}
public void setUuid(String uuid) {
this.uuid = uuid;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public BaseMsg() {
}
public BaseMsg(String uuid, String type, String message) {
this.uuid = uuid;
this.type = type;
this.message = message;
}
@Override
public String toString() {
return "BaseMsg{" +
"uuid='" + uuid + '\'' +
", type='" + type + '\'' +
", message='" + message + '\'' +
'}';
}
}
package com.zhiwei.live.danmu.douyu.entity;
/**
* 功能描述:弹幕消息
*
* @auther: coffee
* @date: 2018-07-04 19:44:16
* 修改日志:
*/
public class ChatMsg implements MsgEntity {
/**
* 原消息
*/
private String message;
/**
* 原消息ID
*/
private String uuid;
/**
* 弹幕唯一ID
*/
private String cid;
/**
* 弹幕组ID
*/
private String gid;
/**
* 房间ID
*/
private String rid;
/**
* 发送者 uid
*/
private String uid;
/**
* 用户昵称
*/
private String nn;
/**
* 消息内容
*/
private String txt;
/**
* 用户等级
*/
private String level;
/**
* 礼物头衔:默认值 0(表示没有头衔)
*/
private String gt;
/**
* 消息颜色:默认值 0(表示默认颜色弹幕)
*/
private String col;
/**
* 客户端类型:默认值 0
*/
private String ct;
/**
* 房间权限组:默认值 1(表示普通权限用户)
*/
private String rg;
/**
* 平台权限组:默认值 1(表示普通权限用户)
*/
private String pg;
/**
* 弹幕具体类型: 默认值 0(普通弹幕)
*/
private String cmt;
/**
* 用户头像
*/
private String ic;
/**
* 贵族等级
*/
private String nl;
/**
* 贵族弹幕标识,0-非贵族弹幕,1-贵族弹幕,默认值 0
*/
private String nc;
/**
* 粉丝牌名称
*/
private String bnn;
/**
* 粉丝牌等级
*/
private String bl;
/**
* 粉丝牌关联房间号
*/
private String brid;
/**
* 徽章信息校验码
*/
private String hc;
/**
* 主播等级
*/
private String ol;
/**
* 是否反向弹幕标记: 0-普通弹幕,1-反向弹幕, 默认值 0
*/
private String rev;
/**
* 是否高亮弹幕标记: 0-普通,1-高亮, 默认值 0
*/
private String hl;
/**
* 是否粉丝弹幕标记: 0-非粉丝弹幕,1-粉丝弹幕, 默认值 0
*/
private String ifs;
/**
* 弹幕发送时间
*/
private String cst;
public String getCid() {
return cid;
}
public void setCid(String cid) {
this.cid = cid;
}
public String getGid() {
return gid;
}
public void setGid(String gid) {
this.gid = gid;
}
public String getRid() {
return rid;
}
public void setRid(String rid) {
this.rid = rid;
}
public String getUid() {
return uid;
}
public void setUid(String uid) {
this.uid = uid;
}
public String getNn() {
return nn;
}
public void setNn(String nn) {
this.nn = nn;
}
public String getTxt() {
return txt;
}
public void setTxt(String txt) {
this.txt = txt;
}
public String getLevel() {
return level;
}
public void setLevel(String level) {
this.level = level;
}
public String getGt() {
return gt;
}
public void setGt(String gt) {
this.gt = gt;
}
public String getCol() {
return col;
}
public void setCol(String col) {
this.col = col;
}
public String getCt() {
return ct;
}
public void setCt(String ct) {
this.ct = ct;
}
public String getRg() {
return rg;
}
public void setRg(String rg) {
this.rg = rg;
}
public String getPg() {
return pg;
}
public void setPg(String pg) {
this.pg = pg;
}
public String getCmt() {
return cmt;
}
public void setCmt(String cmt) {
this.cmt = cmt;
}
public String getIc() {
return ic;
}
public void setIc(String ic) {
this.ic = ic;
}
public String getNl() {
return nl;
}
public void setNl(String nl) {
this.nl = nl;
}
public String getNc() {
return nc;
}
public void setNc(String nc) {
this.nc = nc;
}
public String getBnn() {
return bnn;
}
public void setBnn(String bnn) {
this.bnn = bnn;
}
public String getBl() {
return bl;
}
public void setBl(String bl) {
this.bl = bl;
}
public String getBrid() {
return brid;
}
public void setBrid(String brid) {
this.brid = brid;
}
public String getHc() {
return hc;
}
public void setHc(String hc) {
this.hc = hc;
}
public String getOl() {
return ol;
}
public void setOl(String ol) {
this.ol = ol;
}
public String getRev() {
return rev;
}
public void setRev(String rev) {
this.rev = rev;
}
public String getHl() {
return hl;
}
public void setHl(String hl) {
this.hl = hl;
}
public String getIfs() {
return ifs;
}
public void setIfs(String ifs) {
this.ifs = ifs;
}
public String getCst() {
return cst;
}
public void setCst(String cst) {
this.cst = cst;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
@Override
public String getUuid() {
return uuid;
}
@Override
public void setUuid(String uuid) {
this.uuid = uuid;
}
@Override
public String toString() {
return "ChatMsg{" +
"cid='" + cid + '\'' +
", gid='" + gid + '\'' +
", rid='" + rid + '\'' +
", uid='" + uid + '\'' +
", nn='" + nn + '\'' +
", txt='" + txt + '\'' +
", level='" + level + '\'' +
", gt='" + gt + '\'' +
", col='" + col + '\'' +
", ct='" + ct + '\'' +
", rg='" + rg + '\'' +
", pg='" + pg + '\'' +
", cmt='" + cmt + '\'' +
", ic='" + ic + '\'' +
", nl='" + nl + '\'' +
", nc='" + nc + '\'' +
", bnn='" + bnn + '\'' +
", bl='" + bl + '\'' +
", brid='" + brid + '\'' +
", hc='" + hc + '\'' +
", ol='" + ol + '\'' +
", rev='" + rev + '\'' +
", hl='" + hl + '\'' +
", ifs='" + ifs + '\'' +
", cst='" + cst + '\'' +
'}';
}
/**
* 转换为弹幕消息展示 (name: txt)
*
* @return
*/
public String toChatStr() {
return String.format("%s: %s", nn, txt);
}
}
package com.zhiwei.live.danmu.douyu.entity;
/**
*
* 功能描述:赠送礼物
*
* @auther: coffee
* @date: 2018-07-08 23:59:55
* 修改日志:
*
*/
public class DgbMsg implements MsgEntity{
/**
* 原消息
*/
private String message;
/**
* 原消息ID
*/
private String uuid;
/**
* 房间ID
*/
private String rid;
/**
* 弹幕组ID
*/
private String gid;
/**
* 礼物ID
*/
private String gfid;
/**
* 礼物显示样式
*/
private String gs;
/**
* 用户ID
*/
private String uid;
/**
* 用户昵称
*/
private String nn;
/**
* 大礼物标识:默认值为 0(表示是小礼物)
*/
private String bg;
/**
* 礼物个数:默认值 1(表示 1 个礼物)
*/
private String gfcnt;
/**
* 礼物连击次数:默认值 1(表示 1 连击)
*/
private String hits;
/**
* 贵族等级
*/
private String nl;
/**
* 粉丝牌名称
*/
private String bnn;
/**
* 粉丝牌等级
*/
private String bl;
/**
* 粉丝牌关联房间号
*/
private String brid;
/**
* 徽章信息校验码
*/
private String hc;
public String getRid() {
return rid;
}
public void setRid(String rid) {
this.rid = rid;
}
public String getGid() {
return gid;
}
public void setGid(String gid) {
this.gid = gid;
}
public String getGfid() {
return gfid;
}
public void setGfid(String gfid) {
this.gfid = gfid;
}
public String getGs() {
return gs;
}
public void setGs(String gs) {
this.gs = gs;
}
public String getUid() {
return uid;
}
public void setUid(String uid) {
this.uid = uid;
}
public String getNn() {
return nn;
}
public void setNn(String nn) {
this.nn = nn;
}
public String getBg() {
return bg;
}
public void setBg(String bg) {
this.bg = bg;
}
public String getGfcnt() {
return gfcnt;
}
public void setGfcnt(String gfcnt) {
this.gfcnt = gfcnt;
}
public String getHits() {
return hits;
}
public void setHits(String hits) {
this.hits = hits;
}
public String getNl() {
return nl;
}
public void setNl(String nl) {
this.nl = nl;
}
public String getBnn() {
return bnn;
}
public void setBnn(String bnn) {
this.bnn = bnn;
}
public String getBl() {
return bl;
}
public void setBl(String bl) {
this.bl = bl;
}
public String getBrid() {
return brid;
}
public void setBrid(String brid) {
this.brid = brid;
}
public String getHc() {
return hc;
}
public void setHc(String hc) {
this.hc = hc;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
@Override
public String getUuid() {
return uuid;
}
@Override
public void setUuid(String uuid) {
this.uuid = uuid;
}
@Override
public String toString() {
return "DgbMsg{" +
"rid='" + rid + '\'' +
", gid='" + gid + '\'' +
", gfid='" + gfid + '\'' +
", gs='" + gs + '\'' +
", uid='" + uid + '\'' +
", nn='" + nn + '\'' +
", bg='" + bg + '\'' +
", gfcnt='" + gfcnt + '\'' +
", hits='" + hits + '\'' +
", nl='" + nl + '\'' +
", bnn='" + bnn + '\'' +
", bl='" + bl + '\'' +
", brid='" + brid + '\'' +
", hc='" + hc + '\'' +
'}';
}
}
package com.zhiwei.live.danmu.douyu.entity;
/**
* 功能描述:错误消息
* <p>
* 0 操作成功
* 51 数据传输出错
* 52 服务器关闭
* 204 房间id错误
*
* @auther: coffee
* @date: 2018-07-16 16:09:02
* 修改日志:
*/
public class ErrorMsg implements MsgEntity {
/**
* 原消息
*/
private String message;
/**
* 原消息ID
*/
private String uuid;
/**
* 错误代码
*/
private String code;
/**
* 错误描述
*/
private String desc;
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getDesc() {
if (desc == null && code != null) {
switch (code) {
case "0":
desc = "斗鱼弹幕客户端-操作成功";
break;
case "51":
desc = "斗鱼弹幕客户端-数据传输出错";
break;
case "52":
desc = "斗鱼弹幕客户端-服务已登出 或 服务器关闭";
break;
case "204":
desc = "斗鱼弹幕客户端-房间ID错误";
break;
}
}
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
@Override
public String getUuid() {
return uuid;
}
@Override
public void setUuid(String uuid) {
this.uuid = uuid;
}
}
package com.zhiwei.live.danmu.douyu.entity;
/**
* 功能描述:房间内用户抢红包消息
*
* @auther: coffee
* @date: 2018-07-09 11:36:12
* 修改日志:
*
*/
public class GgbbMsg implements MsgEntity{
/**
* 原消息
*/
private String message;
/**
* 原消息ID
*/
private String uuid;
/**
* 房间ID
*/
private String rid;
/**
* 弹幕组ID
*/
private String gid;
/**
* 抢到的鱼丸数量
*/
private String sl;
/**
* 礼包产生者 id
*/
private String sid;
/**
* 抢礼包者 id
*/
private String did;
/**
* 礼包产生者昵称
*/
private String snk;
/**
* 抢礼包者昵称
*/
private String dnk;
/**
* 礼包类型
*/
private String rpt;
public String getRid() {
return rid;
}
public void setRid(String rid) {
this.rid = rid;
}
public String getGid() {
return gid;
}
public void setGid(String gid) {
this.gid = gid;
}
public String getSl() {
return sl;
}
public void setSl(String sl) {
this.sl = sl;
}
public String getSid() {
return sid;
}
public void setSid(String sid) {
this.sid = sid;
}
public String getDid() {
return did;
}
public void setDid(String did) {
this.did = did;
}
public String getSnk() {
return snk;
}
public void setSnk(String snk) {
this.snk = snk;
}
public String getDnk() {
return dnk;
}
public void setDnk(String dnk) {
this.dnk = dnk;
}
public String getRpt() {
return rpt;
}
public void setRpt(String rpt) {
this.rpt = rpt;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
@Override
public String getUuid() {
return uuid;
}
@Override
public void setUuid(String uuid) {
this.uuid = uuid;
}
@Override
public String toString() {
return "GgbbMsg{" +
"rid='" + rid + '\'' +
", gid='" + gid + '\'' +
", sl='" + sl + '\'' +
", sid='" + sid + '\'' +
", did='" + did + '\'' +
", snk='" + snk + '\'' +
", dnk='" + dnk + '\'' +
", rpt='" + rpt + '\'' +
'}';
}
}
package com.zhiwei.live.danmu.douyu.entity;
/**
*
* 功能描述:
*
* @auther: coffee
* @date: 2018-07-07 17:27:36
* 修改日志:
*
*/
public interface MsgEntity {
String getMessage();
void setMessage(String message);
String getUuid();
void setUuid(String uuid);
}
package com.zhiwei.live.danmu.douyu.entity;
/**
*
* 功能描述:礼物广播消息
*
* @auther: coffee
* @date: 2018-07-09 10:28:48
* 修改日志:
*
*/
public class SpbcMsg implements MsgEntity{
/**
* 原消息
*/
private String message;
/**
* 原消息ID
*/
private String uuid;
/**
* 房间ID
*/
private String rid;
/**
* 弹幕组ID
*/
private String gid;
/**
* 赠送者昵称
*/
private String sn;
/**
* 受赠者昵称
*/
private String dn;
/**
* 礼物名称
*/
private String gn;
/**
* 礼物数量
*/
private String gc;
/**
* 赠送房间
*/
private String drid;
/**
* 广播样式
*/
private String gs;
/**
* 是否有礼包(0-无礼包,1-有礼包)
*/
private String gb;
/**
* 广播展现样式(1-火箭,2-飞机)
*/
private String es;
/**
* 礼物 id
*/
private String gfid;
/**
* 特效 id
*/
private String eid;
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
@Override
public String getUuid() {
return uuid;
}
@Override
public void setUuid(String uuid) {
this.uuid = uuid;
}
public String getRid() {
return rid;
}
public void setRid(String rid) {
this.rid = rid;
}
public String getGid() {
return gid;
}
public void setGid(String gid) {
this.gid = gid;
}
public String getSn() {
return sn;
}
public void setSn(String sn) {
this.sn = sn;
}
public String getDn() {
return dn;
}
public void setDn(String dn) {
this.dn = dn;
}
public String getGn() {
return gn;
}
public void setGn(String gn) {
this.gn = gn;
}
public String getGc() {
return gc;
}
public void setGc(String gc) {
this.gc = gc;
}
public String getDrid() {
return drid;
}
public void setDrid(String drid) {
this.drid = drid;
}
public String getGs() {
return gs;
}
public void setGs(String gs) {
this.gs = gs;
}
public String getGb() {
return gb;
}
public void setGb(String gb) {
this.gb = gb;
}
public String getEs() {
return es;
}
public void setEs(String es) {
this.es = es;
}
public String getGfid() {
return gfid;
}
public void setGfid(String gfid) {
this.gfid = gfid;
}
public String getEid() {
return eid;
}
public void setEid(String eid) {
this.eid = eid;
}
@Override
public String toString() {
return "SpbcMsg{" +
"rid='" + rid + '\'' +
", gid='" + gid + '\'' +
", sn='" + sn + '\'' +
", dn='" + dn + '\'' +
", gn='" + gn + '\'' +
", gc='" + gc + '\'' +
", drid='" + drid + '\'' +
", gs='" + gs + '\'' +
", gb='" + gb + '\'' +
", es='" + es + '\'' +
", gfid='" + gfid + '\'' +
", eid='" + eid + '\'' +
'}';
}
}
package com.zhiwei.live.danmu.douyu.entity;
/**
*
* 功能描述:超级弹幕消息
*
* @auther: coffee
* @date: 2018-07-09 10:24:53
* 修改日志:
*
*/
public class SsdMsg implements MsgEntity{
/**
* 原消息
*/
private String message;
/**
* 原消息ID
*/
private String uuid;
/**
* 超级弹幕ID
*/
private String sdid;
/**
* 房间ID
*/
private String rid;
/**
* 弹幕组ID
*/
private String gid;
/**
* 跳转房间ID
*/
private String trid;
/**
* 超级弹幕内容
*/
private String content;
public String getSdid() {
return sdid;
}
public void setSdid(String sdid) {
this.sdid = sdid;
}
public String getRid() {
return rid;
}
public void setRid(String rid) {
this.rid = rid;
}
public String getGid() {
return gid;
}
public void setGid(String gid) {
this.gid = gid;
}
public String getTrid() {
return trid;
}
public void setTrid(String trid) {
this.trid = trid;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
@Override
public String getUuid() {
return uuid;
}
@Override
public void setUuid(String uuid) {
this.uuid = uuid;
}
@Override
public String toString() {
return "SsdMsg{" +
"sdid='" + sdid + '\'' +
", rid='" + rid + '\'' +
", gid='" + gid + '\'' +
", trid='" + trid + '\'' +
", content='" + content + '\'' +
'}';
}
}
package com.zhiwei.live.danmu.douyu.entity;
/**
*
* 功能描述:用户进房通知消息
*
* @auther: coffee
* @date: 2018-07-09 00:28:36
* 修改日志:
*
*/
public class UenterMsg implements MsgEntity{
/**
* 原消息
*/
private String message;
/**
* 原消息ID
*/
private String uuid;
/**
* 房间ID
*/
private String rid;
/**
* 弹幕组ID
*/
private String gid;
/**
* 用户ID
*/
private String uid;
/**
* 用户昵称
*/
private String nn;
/**
* 用户等级
*/
private String level;
/**
* 礼物头衔:默认值 0(表示没有头衔)
*/
private String gt;
/**
* 房间权限组:默认值 1(表示普通权限用户)
*/
private String rg;
/**
* 平台权限组:默认值 1(表示普通权限用户)
*/
private String pg;
/**
* 用户头像
*/
private String ic;
/**
* 贵族等级
*/
private String nl;
public String getRid() {
return rid;
}
public void setRid(String rid) {
this.rid = rid;
}
public String getGid() {
return gid;
}
public void setGid(String gid) {
this.gid = gid;
}
public String getUid() {
return uid;
}
public void setUid(String uid) {
this.uid = uid;
}
public String getNn() {
return nn;
}
public void setNn(String nn) {
this.nn = nn;
}
public String getLevel() {
return level;
}
public void setLevel(String level) {
this.level = level;
}
public String getGt() {
return gt;
}
public void setGt(String gt) {
this.gt = gt;
}
public String getRg() {
return rg;
}
public void setRg(String rg) {
this.rg = rg;
}
public String getPg() {
return pg;
}
public void setPg(String pg) {
this.pg = pg;
}
public String getIc() {
return ic;
}
public void setIc(String ic) {
this.ic = ic;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public String getUuid() {
return uuid;
}
public void setUuid(String uuid) {
this.uuid = uuid;
}
public String getNl() {
return nl;
}
public void setNl(String nl) {
this.nl = nl;
}
@Override
public String toString() {
return "UenterMsg{" +
"rid='" + rid + '\'' +
", gid='" + gid + '\'' +
", uid='" + uid + '\'' +
", nn='" + nn + '\'' +
", level='" + level + '\'' +
", gt='" + gt + '\'' +
", rg='" + rg + '\'' +
", pg='" + pg + '\'' +
", ic='" + ic + '\'' +
", nl='" + nl + '\'' +
'}';
}
}
package com.zhiwei.live.danmu.douyu.enums;
/**
* 功能描述:贵族等级
*
* @auther: coffee
* @date: 2018-07-08 23:38:22
* 修改日志:
*/
public enum NobleLevel {
LEVEL_0("0", "游侠"),
LEVEL_1("1", "骑士"),
LEVEL_2("2", "子爵"),
LEVEL_3("3", "伯爵"),
LEVEL_4("4", "公爵"),
LEVEL_5("5", "国外"),
LEVEL_6("6", "皇帝"),;
NobleLevel(String code, String description) {
this.code = code;
this.description = description;
}
private String code;
private String description;
}
package com.zhiwei.live.danmu.douyu.exceptions;
/**
*
* 功能描述:
*
* @auther: coffee
* @date: 2018-07-04 15:50:12
* 修改日志:
*
*/
public class DouYuSDKException extends RuntimeException{
private static final long serialVersionUID = -2974352294232725589L;
public DouYuSDKException(Exception e) {
super(e);
}
}
package com.zhiwei.live.danmu.douyu.exceptions;
/**
* 工具类异常
*
* @author coffee
*/
public class UtilException extends RuntimeException {
private static final long serialVersionUID = 8247610319171014183L;
public UtilException(Throwable e) {
super(UtilException.getMessage(e), e);
}
public UtilException(String message) {
super(message);
}
public UtilException(String messageTemplate, Object... params) {
super(String.format(messageTemplate, params));
}
public UtilException(String message, Throwable throwable) {
super(message, throwable);
}
public UtilException(Throwable throwable, String messageTemplate, Object... params) {
super(String.format(messageTemplate, params), throwable);
}
/**
* 获得完整消息,包括异常名
*
* @param e 异常
* @return 完整消息
*/
public static String getMessage(Throwable e) {
if (null == e) {
return "null";
}
return String.format("{}: {}", e.getClass().getSimpleName(), e.getMessage());
}
}
package com.zhiwei.live.danmu.douyu.listener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.zhiwei.live.danmu.douyu.MessageListener;
import com.zhiwei.live.danmu.douyu.entity.BaseMsg;
/**
*
* 功能描述:默认的BaseMsg消息监听器
*
* @auther: coffee
* @date: 2018-07-07 18:48:48
* 修改日志:
*
*/
public class DefaultBaseMsgListener extends MessageListener<BaseMsg> {
private final static Logger logger = LoggerFactory.getLogger(DefaultBaseMsgListener.class);
@Override
public void read(BaseMsg message) {
logger.info(message.toString());
}
}
package com.zhiwei.live.danmu.douyu.listener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.zhiwei.live.danmu.douyu.MessageListener;
import com.zhiwei.live.danmu.douyu.entity.ChatMsg;
/**
* 功能描述:默认ChatMsg消息监听处理器
*
* @auther: coffee
* @date: 2018-07-08 21:23:08
* 修改日志:
*/
public class DefaultChatMsgListener extends MessageListener<ChatMsg> {
private final static Logger logger = LoggerFactory.getLogger(DefaultBaseMsgListener.class);
@Override
public void read(ChatMsg message) {
logger.info(message.toChatStr());
}
}
package com.zhiwei.live.danmu.douyu.util;
import java.lang.reflect.Array;
/**
* 数组工具类
*
* @author Looly
*
*/
public class ArrayUtil {
/**
* 将新元素添加到已有数组中<br>
* 添加新元素会生成一个新的数组,不影响原数组
*
* @param <T> 数组元素类型
* @param buffer 已有数组
* @param newElements 新元素
* @return 新数组
*/
@SafeVarargs
public static <T> T[] append(T[] buffer, T... newElements) {
if(isEmpty(buffer)) {
return newElements;
}
return insert(buffer, buffer.length, newElements);
}
/**
* 将新元素插入到到已有数组中的某个位置<br>
* 添加新元素会生成一个新的数组,不影响原数组<br>
* 如果插入位置为为负数,从原数组从后向前计数,若大于原数组长度,则空白处用null填充
*
* @param <T> 数组元素类型
* @param buffer 已有数组
* @param index 插入位置,此位置为对应此位置元素之前的空档
* @param newElements 新元素
* @return 新数组
* @since 4.0.8
*/
@SafeVarargs
public static <T> T[] insert(T[] buffer, int index, T... newElements) {
if (isEmpty(newElements)) {
return buffer;
}
if(isEmpty(buffer)) {
return newElements;
}
if (index < 0) {
index = (index % buffer.length) + buffer.length;
}
final T[] result = newArray(buffer.getClass().getComponentType(), Math.max(buffer.length, index) + newElements.length);
System.arraycopy(buffer, 0, result, 0, Math.min(buffer.length, index));
System.arraycopy(newElements, 0, result, index, newElements.length);
if (index < buffer.length) {
System.arraycopy(buffer, index, result, index + newElements.length, buffer.length - index);
}
return result;
}
// ---------------------------------------------------------------------- isEmpty
/**
* 数组是否为空
*
* @param <T> 数组元素类型
* @param array 数组
* @return 是否为空
*/
@SuppressWarnings("unchecked")
public static <T> boolean isEmpty(final T... array) {
return array == null || array.length == 0;
}
// ---------------------------------------------------------------------- isNotEmpty
/**
* 数组是否为非空
*
* @param <T> 数组元素类型
* @param array 数组
* @return 是否为非空
*/
@SuppressWarnings("unchecked")
public static <T> boolean isNotEmpty(final T... array) {
return (array != null && array.length != 0);
}
/**
* 新建一个空数组
*
* @param <T> 数组元素类型
* @param componentType 元素类型
* @param newSize 大小
* @return 空数组
*/
@SuppressWarnings("unchecked")
public static <T> T[] newArray(Class<?> componentType, int newSize) {
return (T[]) Array.newInstance(componentType, newSize);
}
/**
* 新建一个空数组
*
* @param <T> 数组元素类型
* @param newSize 大小
* @return 空数组
* @since 3.3.0
*/
@SuppressWarnings("unchecked")
public static <T> T[] newArray(int newSize) {
return (T[]) new Object[newSize];
}
}
package com.zhiwei.live.danmu.douyu.util;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* 功能描述:数据处理工具类
*
* @auther: coffee
* @date: 2018-07-07 16:23:11
* 修改日志:
*/
public class DataUtil {
static Pattern nickNamePattern = Pattern.compile("/nn@=(.*?)/");
static Pattern msgTextPattern = Pattern.compile("/txt@=(.*?)/");
static Pattern typePattern = Pattern.compile("type@=(.*?)/");
private static String match(String source, Pattern pattern, int groupId) {
Matcher matcher = pattern.matcher(source);
if (matcher.find()) {
return matcher.group(groupId);
} else {
return "";
}
}
public static String getNickName(String source) {
return match(source, nickNamePattern, 1);
}
public static String getMsg(String source) {
return match(source, msgTextPattern, 1);
}
/**
* 从消息中获取消息类型
* @param source
* @return
*/
public static String getMsgType(String source) {
return match(source, typePattern, 1);
}
}
package com.zhiwei.live.danmu.douyu.util;
import java.lang.reflect.Field;
import com.zhiwei.live.danmu.douyu.exceptions.UtilException;
/**
* 反射工具类
*
* @author coffee
*/
public class ReflectUtil {
/**
* 字段缓存
*/
private static final SimpleCache<Class<?>, Field[]> FIELDS_CACHE = new SimpleCache<>();
/**
* 获得一个类中所有字段列表,包括其父类中的字段
*
* @param beanClass 类
* @return 字段列表
* @throws SecurityException 安全检查异常
*/
public static Field[] getFields(Class<?> beanClass) throws SecurityException {
Field[] allFields = FIELDS_CACHE.get(beanClass);
if (null != allFields) {
return allFields;
}
allFields = getFieldsDirectly(beanClass, true);
return FIELDS_CACHE.put(beanClass, allFields);
}
/**
* 获得一个类中所有字段列表,直接反射获取,无缓存
*
* @param beanClass 类
* @param withSuperClassFieds 是否包括父类的字段列表
* @return 字段列表
* @throws SecurityException 安全检查异常
*/
public static Field[] getFieldsDirectly(Class<?> beanClass, boolean withSuperClassFieds) throws SecurityException {
if (beanClass == null) {
throw new UtilException("反射获取字段出现异常,参数 beanClass 为空");
}
Field[] allFields = null;
Class<?> searchType = beanClass;
Field[] declaredFields;
while (searchType != null) {
declaredFields = searchType.getDeclaredFields();
if (null == allFields) {
allFields = declaredFields;
} else {
allFields = ArrayUtil.append(allFields, declaredFields);
}
searchType = withSuperClassFieds ? searchType.getSuperclass() : null;
}
return allFields;
}
/**
* 查找指定类中的所有字段(包括非public字段),也包括父类和Object类的字段, 字段不存在则返回<code>null</code>
*
* @param beanClass 被查找字段的类,不能为null
* @param name 字段名
* @return 字段
* @throws SecurityException 安全异常
*/
public static Field getField(Class<?> beanClass, String name) throws SecurityException {
final Field[] fields = getFields(beanClass);
if (ArrayUtil.isNotEmpty(fields)) {
for (Field field : fields) {
if ((name.equals(field.getName()))) {
return field;
}
}
}
return null;
}
/**
* 设置字段值
*
* @param obj 对象
* @param fieldName 字段名
* @param value 值,值类型必须与字段类型匹配,不会自动转换对象类型
* @throws UtilException 包装IllegalAccessException异常
*/
public static void setFieldValue(Object obj, String fieldName, Object value) throws UtilException {
if (obj == null) {
throw new UtilException("[Assertion failed] - this argument is required; it must not be null");
}
if (fieldName == null || "".equals(fieldName)) {
throw new UtilException("[Assertion failed] - this String argument must have text; it must not be null, empty, or blank");
}
setFieldValue(obj, getField(obj.getClass(), fieldName), value);
}
/**
* 设置字段值
*
* @param obj 对象
* @param field 字段
* @param value 值,值类型必须与字段类型匹配,不会自动转换对象类型
* @throws UtilException UtilException 包装IllegalAccessException异常
*/
public static void setFieldValue(Object obj, Field field, Object value) throws UtilException {
if (obj == null) {
throw new UtilException("[Assertion failed] - this argument is required; it must not be null");
}
if (field == null) {
throw new UtilException("[Assertion failed] - this argument is required; it must not be null");
}
field.setAccessible(true);
try {
field.set(obj, value);
} catch (IllegalAccessException e) {
throw new UtilException(e, "IllegalAccess for {}.{}", obj.getClass(), field.getName());
}
}
/**
* 实例化对象
*
* @param <T> 对象类型
* @param clazz 类名
* @return 对象
* @throws UtilException 包装各类异常
*/
@SuppressWarnings("unchecked")
public static <T> T newInstance(String clazz) throws UtilException {
try {
return (T) Class.forName(clazz).newInstance();
} catch (Exception e) {
throw new UtilException(e, "Instance class [{}] error!", clazz);
}
}
/**
* 实例化对象
*
* @param clazz 对象类型
* @param <T> 类名
* @return 对象
* @throws UtilException 包装各类异常
*/
public static <T> T newInstance(Class<T> clazz) throws UtilException {
try {
return (T) clazz.newInstance();
} catch (Exception e) {
throw new UtilException(e, "Instance class [{}] error!", clazz);
}
}
}
package com.zhiwei.live.danmu.douyu.util;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
/**
* 简单缓存,无超时实现,使用{@link WeakHashMap}实现缓存自动清理
*
* @param <K> 键类型
* @param <V> 值类型
* @author Looly
*/
public class SimpleCache<K, V> {
/**
* 池
*/
private final Map<K, V> cache = new WeakHashMap<>();
private final ReentrantReadWriteLock cacheLock = new ReentrantReadWriteLock();
private final ReadLock readLock = cacheLock.readLock();
private final WriteLock writeLock = cacheLock.writeLock();
/**
* 从缓存池中查找值
*
* @param key 键
* @return 值
*/
public V get(K key) {
// 尝试读取缓存
readLock.lock();
V value;
try {
value = cache.get(key);
} finally {
readLock.unlock();
}
return value;
}
/**
* 放入缓存
*
* @param key 键
* @param value 值
* @return 值
*/
public V put(K key, V value) {
writeLock.lock();
try {
cache.put(key, value);
} finally {
writeLock.unlock();
}
return value;
}
/**
* 移除缓存
*
* @param key 键
* @return 移除的值
*/
public V remove(K key) {
writeLock.lock();
try {
return cache.remove(key);
} finally {
writeLock.unlock();
}
}
/**
* 清空缓存池
*/
public void clear() {
writeLock.lock();
try {
this.cache.clear();
} finally {
writeLock.unlock();
}
}
}
package com.zhiwei.live.danmu.douyu.util;
import java.util.UUID;
/**
*
* 功能描述:UUID工具类
*
* @auther: coffee
* @date: 2018-07-07 18:26:09
* 修改日志:
*
*/
public class UUIDUtil {
/**
* @return 随机UUID
*/
public static String randomUUID() {
return UUID.randomUUID().toString();
}
/**
* 简化的UUID,去掉了横线
*
* @return 简化的UUID,去掉了横线
* @since 3.2.2
*/
public static String simpleUUID() {
return randomUUID().replace("-", "");
}
}
package com.zhiwei.live.danmu.pandamtv; package com.zhiwei.live.danmu.pandam;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import com.zhiwei.crawler.core.HttpBoot; import com.zhiwei.crawler.core.HttpBoot;
import com.zhiwei.crawler.core.RequestUtils; import com.zhiwei.crawler.core.RequestUtils;
import com.zhiwei.live.danmu.util.Connector; import com.zhiwei.live.danmu.util.Connector;
import com.zhiwei.live.danmu.util.DataCallBack;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.handler.timeout.IdleStateHandler; import io.netty.channel.nio.NioEventLoopGroup;
import okhttp3.Request; import io.netty.handler.timeout.IdleStateHandler;
import okhttp3.Response; import okhttp3.Request;
import okhttp3.Response;
/**
* @ClassName: PandaClient /**
* @Description: TODO * @ClassName: PandaClient
* @author 0xff * @Description: TODO
* @date Jan 25, 2019 3:24:33 PM * @author 0xff
*/ * @date Jan 25, 2019 3:24:33 PM
public class PandaClient { */
private static HttpBoot httpBoot = new HttpBoot(); public class PandamClient {
private static HttpBoot httpBoot = new HttpBoot();
public static void main(String[] args) throws Exception {
String roomId = "337852";
Request request = RequestUtils.wrapGet("http://riven.panda.tv/chatroom/getinfo?roomid=" + roomId); /**
String host = null; * 根据房间号获取弹幕信息
int port = -1; * @param roomId
JSONObject json = null; * @throws Exception
*/
try(Response response = httpBoot.syncCall(request)) { public static void getDanmu(DataCallBack dataCallBack,String roomId) throws Exception {
json = JSON.parseObject(response.body().string()); //根据房间号获取弹幕服务器地址
if(json.getInteger("errno") != 0) { Request request = RequestUtils.wrapGet("http://riven.panda.tv/chatroom/getinfo?roomid=" + roomId);
throw new IllegalStateException("房间号: " + roomId + " 不存在"); String host = null;
} int port = -1;
json = json.getJSONObject("data"); JSONObject json = null;
String[] address = json.getJSONArray("chat_addr_list").getString(0).split(":");
host = address[0]; try(Response response = httpBoot.syncCall(request)) {
port = Integer.parseInt(address[1]); json = JSON.parseObject(response.body().string());
} catch(Exception e) { if(json.getInteger("errno") != 0) {
throw new IllegalArgumentException("获取聊天服务器地址失败", e); throw new IllegalStateException("房间号: " + roomId + " 不存在");
} }
String appid = json.getString("appid"); json = json.getJSONObject("data");
String rid = json.getString("rid"); String[] address = json.getJSONArray("chat_addr_list").getString(0).split(":");
String sign = json.getString("sign"); host = address[0];
String authType = json.getString("authType"); port = Integer.parseInt(address[1]);
String ts = json.getString("ts"); } catch(Exception e) {
Connector.asynchronizedTcpConnect(new NioEventLoopGroup(), host, port, throw new IllegalArgumentException("获取聊天服务器地址失败", e);
new IdleStateHandler(0, 0, 45), new PandaMessageHandler(appid,rid,ts, sign, authType)).sync(); }
} String appid = json.getString("appid");
String rid = json.getString("rid");
} String sign = json.getString("sign");
String authType = json.getString("authType");
String ts = json.getString("ts");
Connector.asynchronizedTcpConnect(new NioEventLoopGroup(), host, port,
new IdleStateHandler(0, 30, 45), new PandamMessageHandler(dataCallBack, appid, rid, ts, sign, authType)).sync();
}
}
package com.zhiwei.live.danmu.pandamtv; package com.zhiwei.live.danmu.pandam;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
......
package com.zhiwei.live.danmu.pandamtv; package com.zhiwei.live.danmu.pandam;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import com.zhiwei.live.danmu.util.DataCallBack;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil; import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled; import io.netty.buffer.ByteBufUtil;
import io.netty.channel.ChannelHandlerContext; import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.timeout.IdleState; import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.timeout.IdleStateEvent; import io.netty.handler.timeout.IdleState;
import io.netty.util.CharsetUtil; import io.netty.handler.timeout.IdleStateEvent;
import io.netty.util.ReferenceCountUtil; import io.netty.util.CharsetUtil;
import io.netty.util.ReferenceCountUtil;
/**
* @ClassName: PandaMessageHandler /**
* @Description: TODO * @ClassName: PandaMessageHandler
* @author 0xff * @Description: TODO
* @date Jan 25, 2019 11:03:20 AM * @author 0xff
*/ * @date Jan 25, 2019 11:03:20 AM
public class PandaMessageHandler extends ChannelInboundHandlerAdapter { */
private static final byte[] FIRST_REQ = new byte[] { 0x00, 0x06, 0x00, 0x02, 0x00 }; public class PandamMessageHandler extends ChannelInboundHandlerAdapter {
private static final byte[] FIRST_RES = new byte[] { 0x00, 0x06, 0x00, 0x06 }; private static final byte[] FIRST_REQ = new byte[] { 0x00, 0x06, 0x00, 0x02, 0x00 };
private static final byte[] PING = new byte[] { 0x00, 0x06, 0x00, 0x00 }; private static final byte[] FIRST_RES = new byte[] { 0x00, 0x06, 0x00, 0x06 };
private static final byte[] REQ = new byte[] { 0x00, 0x06, 0x00, 0x03 }; private static final byte[] PING = new byte[] { 0x00, 0x06, 0x00, 0x00 };
private static Pattern pattern = Pattern.compile("\\{\"type\":\"1\".+?\\}\\}"); private static final byte[] REQ = new byte[] { 0x00, 0x06, 0x00, 0x03 };
private String rid; private static Pattern pattern = Pattern.compile("\\{\"type\":\"1\".+?\\}\\}");
private String appid; private String rid;
private String ts; private String appid;
private String sign; private String ts;
private String authType; private String sign;
private String authType;
/** private DataCallBack dataCallBack;
* Constructor
*
* @param roomId /**
* 房间号 * Constructor
*/ *
public PandaMessageHandler(String appid, String rid, String ts, String sign, String authType) { * @param roomId
this.appid = appid; * 房间号
this.rid = rid; */
this.sign = sign; public PandamMessageHandler(DataCallBack dataCallBack,String appid, String rid, String ts, String sign, String authType) {
this.authType = authType; this.dataCallBack = dataCallBack;
this.ts = ts; this.appid = appid;
} this.rid = rid;
this.sign = sign;
/* this.authType = authType;
* @see this.ts = ts;
* io.netty.channel.ChannelInboundHandlerAdapter#channelActive(io.netty.channel.
* ChannelHandlerContext) }
*/
@Override /*
public void channelActive(ChannelHandlerContext ctx) throws Exception { * @see
System.out.println("TCP 连接建立成功: " + ctx.channel()); * io.netty.channel.ChannelInboundHandlerAdapter#channelActive(io.netty.channel.
byte[] body = StringUtils * ChannelHandlerContext)
.join("u:", rid, "@", appid, "\nk:1\nt:300\nts:", ts, "\nsign:", sign, "\nauthtype:", authType) */
.getBytes(); @Override
ByteBuf loginMsg = Unpooled.wrappedBuffer(Unpooled.wrappedBuffer(FIRST_REQ), public void channelActive(ChannelHandlerContext ctx) throws Exception {
Unpooled.buffer(1).writeByte(body.length), Unpooled.wrappedBuffer(body)); System.out.println("TCP 连接建立成功: " + ctx.channel());
System.out.println("发送登录消息: \n" + ByteBufUtil.prettyHexDump(loginMsg)); byte[] body = StringUtils
ctx.writeAndFlush(loginMsg); .join("u:", rid, "@", appid, "\nk:1\nt:300\nts:", ts, "\nsign:", sign, "\nauthtype:", authType)
} .getBytes();
ByteBuf loginMsg = Unpooled.wrappedBuffer(Unpooled.wrappedBuffer(FIRST_REQ),
/* Unpooled.buffer(1).writeByte(body.length), Unpooled.wrappedBuffer(body));
* @see System.out.println("发送登录消息: \n" + ByteBufUtil.prettyHexDump(loginMsg));
* io.netty.channel.ChannelInboundHandlerAdapter#channelRead(io.netty.channel. ctx.writeAndFlush(loginMsg);
* ChannelHandlerContext, java.lang.Object) }
*/
@Override /*
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { * @see
if (msg instanceof ByteBuf) { * io.netty.channel.ChannelInboundHandlerAdapter#channelRead(io.netty.channel.
ByteBuf buf = (ByteBuf) msg; * ChannelHandlerContext, java.lang.Object)
// System.out.println("收到消息: \n" + ByteBufUtil.prettyHexDump(buf)); */
String source = buf.toString(CharsetUtil.UTF_8); @Override
Matcher matcher = pattern.matcher(source); public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
while(matcher.find()) { if (msg instanceof ByteBuf) {
JSONObject dataJson = JSONObject.parseObject(matcher.group()); ByteBuf buf = (ByteBuf) msg;
PandamMessage pandamMessage = new PandamMessage(dataJson); // System.out.println("收到消息: \n" + ByteBufUtil.prettyHexDump(buf));
System.out.println(pandamMessage); String source = buf.toString(CharsetUtil.UTF_8);
} Matcher matcher = pattern.matcher(source);
} while(matcher.find()) {
ReferenceCountUtil.release(msg); JSONObject dataJson = JSONObject.parseObject(matcher.group());
} PandamMessage pandamMessage = new PandamMessage(dataJson);
System.out.println(pandamMessage);
/* }
* @see }
* io.netty.channel.ChannelInboundHandlerAdapter#userEventTriggered(io.netty. ReferenceCountUtil.release(msg);
* channel.ChannelHandlerContext, java.lang.Object) }
*/
@Override /*
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { * @see
if (evt instanceof IdleStateEvent) { * io.netty.channel.ChannelInboundHandlerAdapter#userEventTriggered(io.netty.
IdleStateEvent event = (IdleStateEvent) evt; * channel.ChannelHandlerContext, java.lang.Object)
if (event.state() == IdleState.ALL_IDLE) { */
ByteBuf pingMsg = Unpooled.wrappedBuffer(PING); @Override
System.out.println("发送心跳消息: \n" + ByteBufUtil.prettyHexDump(pingMsg)); public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
ctx.writeAndFlush(pingMsg); if (evt instanceof IdleStateEvent) {
} else { IdleStateEvent event = (IdleStateEvent) evt;
ctx.fireUserEventTriggered(evt); if (event.state() == IdleState.WRITER_IDLE) {
} ByteBuf pingMsg = Unpooled.wrappedBuffer(PING);
} else { System.out.println("发送心跳消息: \n" + ByteBufUtil.prettyHexDump(pingMsg));
ctx.fireUserEventTriggered(evt); ctx.writeAndFlush(pingMsg);
} } else {
} ctx.fireUserEventTriggered(evt);
} }
} else {
ctx.fireUserEventTriggered(evt);
}
}
}
package com.zhiwei.live.danmu.pandamtv;
import java.util.List;
import java.util.Vector;
public class PandamMessageListener extends Thread{
public static List<PandamMessage> messages = new Vector<>();
}
package com.zhiwei.live.danmu.bilibili; package com.zhiwei.live.danmu.util;
import com.zhiwei.live.danmu.bilibili.BilibiliMessage;
import com.zhiwei.live.danmu.douyu.DouYuMessage;
import com.zhiwei.live.danmu.pandam.PandamMessage;
public interface DataCallBack { public interface DataCallBack {
...@@ -10,6 +13,10 @@ public interface DataCallBack { ...@@ -10,6 +13,10 @@ public interface DataCallBack {
* @param attr * @param attr
* @return void * @return void
*/ */
void onData(BilibiliMessage data); void onData(Object message);
} }
package com.zhiwei.live.danmu.douyu.util; package com.zhiwei.live.danmu.util;
import java.lang.reflect.Field;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
...@@ -21,7 +20,7 @@ import java.util.Map; ...@@ -21,7 +20,7 @@ import java.util.Map;
* @date: 2018-07-08 18:18:50 * @date: 2018-07-08 18:18:50
* 修改日志: * 修改日志:
*/ */
public class STTUtil { public class DouYuUtil {
/** /**
* STT字符串转换为Map * STT字符串转换为Map
...@@ -50,25 +49,6 @@ public class STTUtil { ...@@ -50,25 +49,6 @@ public class STTUtil {
return map; return map;
} }
/**
* STT字符串转换为bean
*
* @param sttString
* @param beanClass
* @param <T>
* @return
*/
public static <T> T toBean(String sttString, Class<T> beanClass) {
T obj = ReflectUtil.newInstance(beanClass);
Map<String, Object> dataMap = toMap(sttString);
Field[] fields = ReflectUtil.getFields(beanClass);
for (Field field : fields) {
String fieldName = field.getName();
Object value = dataMap.get(fieldName);
ReflectUtil.setFieldValue(obj, field, value);
}
return obj;
}
/** /**
* 解码key或value中的转码字符<br> * 解码key或value中的转码字符<br>
...@@ -90,18 +70,4 @@ public class STTUtil { ...@@ -90,18 +70,4 @@ public class STTUtil {
return str; return str;
} }
public static void main(String[] args) {
String msg = "type@=loginres/userid@=0/roomgroup@=0/pg@=0/sessionid@=0/username@=/nickname@=/live_stat@=0/is_illegal@=0/ill_ct@=/ill_ts@=0/now@=0/ps@=0/es@=0/it@=0/its@=0/npv@=0/best_dlev@=0/cur_lev@=0/nrc@=0/ih@=0/sid@=72983/sahf@=0/sceneid@=0/";
msg = "type@=uenter/rid@=4835718/uid@=20003668/nn@=须予银鞍绣毂迎倾心人/level@=39/rg@=4/ic@=avatar_v3@S201807@S79596c14f4a7b4dd245d7a0a33f63ca5/nl@=3/rni@=0/el@=eid@AA=1500000113@ASetp@AA=1@ASsc@AA=1@ASef@AA=0@AS@S/sahf@=0/wgei@=0/cbid@=11011/fl@=18/";
Map<String, Object> dataMap = STTUtil.toMap(msg);
for (String key : dataMap.keySet()) {
System.out.println("key:" + key + " val:" + dataMap.get(key));
}
System.out.println(decrypt("eid@AA=1500000113@ASetp@AA=1@ASsc@AA=1@ASef@AA=0@AS@S"));
}
} }
...@@ -29,6 +29,29 @@ public class DouYuRoomInfoCrawler { ...@@ -29,6 +29,29 @@ public class DouYuRoomInfoCrawler {
*/ */
public static RoomInfo getRoomInfoByRoomId(String roomId) throws Exception{ public static RoomInfo getRoomInfoByRoomId(String roomId) throws Exception{
String url = "http://open.douyucdn.cn/api/RoomApi/room/" + roomId; String url = "http://open.douyucdn.cn/api/RoomApi/room/" + roomId;
String htmlBody = httpBoot.syncCall(RequestUtils.wrapGet(url)).body().string();
if(!StringUtils.isBlank(htmlBody)) {
JSONObject data = JSONObject.parseObject(htmlBody).getJSONObject("data");
String room_name = data.getString("room_name");
String user_name = data.getString("owner_name");
Integer hn = data.getInteger("hn");
int online = data.getInteger("online");
return new RoomInfo(PT, roomId, room_name, user_name , hn);
}else {
logger.info("此次采集页面中不包含房间信息字段, 此次页面信息为:{}", htmlBody);
return null;
}
}
/**
* 根据房间id获取房间信息
* @param roomId
* @return
* @throws Exception
*/
public static RoomInfo getRoomInfoProxyByRoomId(String roomId) throws Exception{
String url = "http://open.douyucdn.cn/api/RoomApi/room/" + roomId;
String htmlBody = httpBoot.syncCall(RequestUtils.wrapGet(url),ProxyHolder.NAT_PROXY).body().string(); String htmlBody = httpBoot.syncCall(RequestUtils.wrapGet(url),ProxyHolder.NAT_PROXY).body().string();
if(!StringUtils.isBlank(htmlBody)) { if(!StringUtils.isBlank(htmlBody)) {
JSONObject data = JSONObject.parseObject(htmlBody).getJSONObject("data"); JSONObject data = JSONObject.parseObject(htmlBody).getJSONObject("data");
......
package com.zhiwei.live.test.danmu;
import com.zhiwei.live.danmu.bilibili.BilibiliClient;
import com.zhiwei.live.danmu.bilibili.BilibiliMessage;
import com.zhiwei.live.danmu.util.DataCallBack;
public class BilibiliDanMuTest {
public static void main(String[] args) throws InterruptedException {
String roomUrl = "https://live.bilibili.com/139";
String roomId = roomUrl.replaceAll("https://live.bilibili.com/", "");
try {
BilibiliClient.getDanmu(new DataCallBack() {
@Override
public void onData(Object message) {
if(message instanceof BilibiliMessage) {
BilibiliMessage bilibiliMessage = (BilibiliMessage)message;
System.out.println("-------------" + bilibiliMessage.toString());
}
}
},roomId);
} catch (Exception e) {
e.printStackTrace();
}
}
}
package com.zhiwei.live.test.danmu;
import com.zhiwei.live.danmu.douyu.DouYuMessage;
import com.zhiwei.live.danmu.douyu.DouyuClient;
import com.zhiwei.live.danmu.util.DataCallBack;
public class DouYuDanMuTest {
public static void main(String[] args) throws InterruptedException {
String roomUrl = "https://live.bilibili.com/139";
String roomId = roomUrl.replaceAll("https://live.bilibili.com/", "");
try {
DouyuClient.getDanmu(new DataCallBack() {
@Override
public void onData(Object message) {
if (message instanceof DouYuMessage) {
DouYuMessage douyuMessage = (DouYuMessage) message;
System.out.println("-------------" + douyuMessage.toString());
}
}
}, roomId);
} catch (Exception e) {
e.printStackTrace();
}
}
}
package com.zhiwei.live.test.danmu;
import com.zhiwei.live.danmu.pandam.PandamClient;
import com.zhiwei.live.danmu.pandam.PandamMessage;
import com.zhiwei.live.danmu.util.DataCallBack;
public class PanDamDanMuTest {
public static void main(String[] args) throws Exception {
String roomId = "337852";
try {
PandamClient.getDanmu(new DataCallBack() {
@Override
public void onData(Object message) {
if (message instanceof PandamMessage) {
PandamMessage pandamMessage = (PandamMessage) message;
System.out.println("-------------" + pandamMessage.toString());
}
}
}, roomId);
} catch (Exception e) {
e.printStackTrace();
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment