diff --git a/build.gradle b/build.gradle index b999f9f..27b02fb 100644 --- a/build.gradle +++ b/build.gradle @@ -24,7 +24,7 @@ dependencies { // https://mvnrepository.com/artifact/org.slf4j/slf4j-api compile group: 'org.slf4j', name: 'slf4j-api', version: '1.7.25' // https://mvnrepository.com/artifact/io.netty/netty-all - compile group: 'io.netty', name: 'netty-all', version: '4.1.19.Final' + compile group: 'io.netty', name: 'netty-all', version: '4.1.20.Final' // https://mvnrepository.com/artifact/com.google.guava/guava compile group: 'com.google.guava', name: 'guava', version: '23.6-jre' } diff --git a/record/bullet_screen_stream_json/ACITIVITY_EVENT.json b/record/bullet_screen_stream_json/ACITIVITY_EVENT.json new file mode 100644 index 0000000..a4e40fd --- /dev/null +++ b/record/bullet_screen_stream_json/ACITIVITY_EVENT.json @@ -0,0 +1,9 @@ +{ + "cmd": "ACTIVITY_EVENT", + "data": { + "keyword": "newspring_2018", + "type": "cracker", + "limit": 300000, + "progress": 158912 + } +} diff --git a/record/bullet_screen_stream_json/LIVE.json b/record/bullet_screen_stream_json/LIVE.json index 55fe08f..0d9684b 100644 --- a/record/bullet_screen_stream_json/LIVE.json +++ b/record/bullet_screen_stream_json/LIVE.json @@ -1,4 +1,4 @@ { "cmd": "LIVE", - "roomid": 1110317 + "roomid": "1110317" } diff --git a/record/bullet_screen_stream_json/PREPARING.json b/record/bullet_screen_stream_json/PREPARING.json index e5efee1..2cfea1e 100644 --- a/record/bullet_screen_stream_json/PREPARING.json +++ b/record/bullet_screen_stream_json/PREPARING.json @@ -1,4 +1,4 @@ { "cmd": "PREPARING", - "roomid": 1110317 + "roomid": "1110317" } diff --git a/src/main/java/com/hiczp/bilibili/api/BilibiliAPI.java b/src/main/java/com/hiczp/bilibili/api/BilibiliAPI.java index 42fe04d..7255d93 100644 --- a/src/main/java/com/hiczp/bilibili/api/BilibiliAPI.java +++ b/src/main/java/com/hiczp/bilibili/api/BilibiliAPI.java @@ -55,7 +55,6 @@ public class BilibiliAPI implements BilibiliServiceProvider, LiveClientProvider @Override public PassportService getPassportService() { if (passportService == null) { - LOGGER.debug("Init PassportService in BilibiliAPI instance {}", this.hashCode()); OkHttpClient okHttpClient = new OkHttpClient().newBuilder() .addInterceptor(new AddFixedParamsInterceptor( "build", bilibiliClientProperties.getBuild(), @@ -84,7 +83,6 @@ public class BilibiliAPI implements BilibiliServiceProvider, LiveClientProvider @Override public LiveService getLiveService() { if (liveService == null) { - LOGGER.debug("Init LiveService in BilibiliAPI instance {}", this.hashCode()); OkHttpClient okHttpClient = new OkHttpClient.Builder() .addInterceptor(new AddFixedHeadersInterceptor( "Buvid", bilibiliClientProperties.getBuvId(), diff --git a/src/main/java/com/hiczp/bilibili/api/live/socket/LiveClient.java b/src/main/java/com/hiczp/bilibili/api/live/socket/LiveClient.java index d65b8b9..139aec8 100644 --- a/src/main/java/com/hiczp/bilibili/api/live/socket/LiveClient.java +++ b/src/main/java/com/hiczp/bilibili/api/live/socket/LiveClient.java @@ -1,15 +1,32 @@ package com.hiczp.bilibili.api.live.socket; import com.google.common.eventbus.EventBus; +import com.google.common.eventbus.Subscribe; import com.hiczp.bilibili.api.BilibiliServiceProvider; import com.hiczp.bilibili.api.live.entity.LiveRoomInfoEntity; +import com.hiczp.bilibili.api.live.socket.codec.PackageDecoder; +import com.hiczp.bilibili.api.live.socket.codec.PackageEncoder; +import com.hiczp.bilibili.api.live.socket.event.ConnectSucceedEvent; +import com.hiczp.bilibili.api.live.socket.event.ConnectionCloseEvent; +import com.hiczp.bilibili.api.live.socket.handler.LiveClientHandler; +import io.netty.bootstrap.Bootstrap; +import io.netty.channel.Channel; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.SocketChannel; +import io.netty.channel.socket.nio.NioSocketChannel; +import io.netty.handler.codec.LengthFieldBasedFrameDecoder; +import io.netty.handler.timeout.IdleStateHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.Closeable; import java.io.IOException; import java.util.Optional; +import java.util.concurrent.TimeUnit; -public class LiveClient { +public class LiveClient implements Closeable { private static final Logger LOGGER = LoggerFactory.getLogger(LiveClient.class); private final EventBus eventBus = new EventBus("BilibiliLiveClient"); @@ -17,42 +34,158 @@ public class LiveClient { private final long showRoomId; private final long userId; - private LiveRoomInfoEntity.LiveRoomEntity liveRoomEntity; + private long reconnectLimit = 0; + private long reconnectAttempt = 0; + private long reconnectDelay = 5L; + private boolean userWantClose = false; + + private Long roomId; + private EventLoopGroup eventLoopGroup; + private Channel channel; public LiveClient(BilibiliServiceProvider bilibiliServiceProvider, long showRoomId) { this.bilibiliServiceProvider = bilibiliServiceProvider; this.showRoomId = showRoomId; this.userId = 0; + initEventBus(); } public LiveClient(BilibiliServiceProvider bilibiliServiceProvider, long showRoomId, long userId) { this.bilibiliServiceProvider = bilibiliServiceProvider; this.showRoomId = showRoomId; this.userId = userId; + initEventBus(); } - private void fetchRoomInfo() throws IOException { - liveRoomEntity = bilibiliServiceProvider.getLiveService() + private void initEventBus() { + eventBus.register(this); + } + + public synchronized LiveClient connect() throws IOException { + if (channel != null && channel.isActive()) { + LOGGER.warn("Already connected to server, connect method can not be invoked twice"); + return this; + } + + reconnectAttempt++; + + LOGGER.info("Fetching info of live room {}", showRoomId); + LiveRoomInfoEntity.LiveRoomEntity liveRoomEntity = bilibiliServiceProvider.getLiveService() .getRoomInfo(showRoomId) .execute() .body() .getData(); + roomId = liveRoomEntity.getRoomId(); + LOGGER.info("Get actual room id {}", roomId); + + if (eventLoopGroup != null) { + eventLoopGroup.shutdownGracefully(); + } + eventLoopGroup = new NioEventLoopGroup(1); + LOGGER.debug("Init SocketChannel Bootstrap"); + Bootstrap bootstrap = new Bootstrap() + .group(eventLoopGroup) + .channel(NioSocketChannel.class) + .handler(new ChannelInitializer() { + @Override + protected void initChannel(SocketChannel socketChannel) throws Exception { + socketChannel.pipeline() + .addLast(new LengthFieldBasedFrameDecoder( + Integer.MAX_VALUE, + 0, + Package.LENGTH_FIELD_LENGTH, + -Package.LENGTH_FIELD_LENGTH, + 0 + )) + .addLast(new IdleStateHandler(40, 0, 0)) + //.addLast(new LoggingHandler(LogLevel.DEBUG)) + .addLast(new PackageEncoder()) + .addLast(new PackageDecoder()) + .addLast(new LiveClientHandler(roomId, userId, eventBus)); + } + }); + + String address = liveRoomEntity.getCmt(); + int port = liveRoomEntity.getCmtPortGoim(); + LOGGER.info("Connecting to Bullet Screen server {}:{}", address, port); + try { + channel = bootstrap.connect(address, port) + .sync() + .channel(); + } catch (InterruptedException e) { + e.printStackTrace(); + close(); + } catch (Exception e) { //有可能在此时出现网络错误 + close(); + throw new IOException(e); + } + + return this; } - public LiveClient connect() throws IOException { - LOGGER.info("Try to fetch info of live room {}", showRoomId); - fetchRoomInfo(); - LOGGER.info("Get actual room id {}", liveRoomEntity.getRoomId()); + @Override + public synchronized void close() { + userWantClose = true; + if (eventLoopGroup != null) { + LOGGER.info("Closing connection"); + try { + eventLoopGroup.shutdownGracefully().sync(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + eventLoopGroup = null; + } + } - LOGGER.debug("Init NioEventLoop"); + @Subscribe + public void onConnectSucceed(ConnectSucceedEvent connectSucceedEvent) { + LOGGER.info("Connect succeed"); + if (userWantClose) { + reconnectAttempt = 0; + } + } - throw new UnsupportedOperationException(); + @Subscribe + public void onConnectionClose(ConnectionCloseEvent connectionCloseEvent) { + LOGGER.info("Connection closed"); + if (!userWantClose && reconnectAttempt <= reconnectLimit) { + LOGGER.info("Reconnect attempt {}, limit {}", reconnectAttempt, reconnectLimit); + LOGGER.info("Auto reconnect after {} second", reconnectDelay); + eventLoopGroup.schedule( + () -> { + try { + connect(); + } catch (IOException e) { + e.printStackTrace(); + } + }, + reconnectDelay, + TimeUnit.SECONDS + ); + } else { + eventLoopGroup.shutdownGracefully(); + } + + if (userWantClose) { + userWantClose = false; + reconnectAttempt = 0; + } } public EventBus getEventBus() { return eventBus; } + public LiveClient registerListener(Object object) { + eventBus.register(object); + return this; + } + + public LiveClient unregisterListeners(Object object) { + eventBus.unregister(object); + return this; + } + public long getShowRoomId() { return showRoomId; } @@ -61,7 +194,17 @@ public class LiveClient { return userId; } - public Optional getLiveRoomEntity() { - return Optional.of(liveRoomEntity); + public Optional getRoomId() { + return Optional.of(roomId); + } + + public LiveClient setReconnectLimit(long reconnectLimit) { + this.reconnectLimit = reconnectLimit; + return this; + } + + public LiveClient setReconnectDelay(long reconnectDelay) { + this.reconnectDelay = reconnectDelay; + return this; } } diff --git a/src/main/java/com/hiczp/bilibili/api/live/socket/Package.java b/src/main/java/com/hiczp/bilibili/api/live/socket/Package.java new file mode 100644 index 0000000..89f8843 --- /dev/null +++ b/src/main/java/com/hiczp/bilibili/api/live/socket/Package.java @@ -0,0 +1,115 @@ +package com.hiczp.bilibili.api.live.socket; + +//数据包结构说明 +//00 00 00 28 00 10 00 00 00 00 00 07 00 00 00 00 +//00 00 00 28/00 10/00 00 00 00 00 07/00 00 00 00 +//1-4 字节: 数据包长度 +//5-6 字节: 协议头长度, 固定值 0x10 +//7-8 字节: 设备类型, Android 固定为 0 +//9-12 字节: 数据包类型 +//13-16 字节: 设备类型, 同 7-8 字节 +public class Package { + public static final short LENGTH_FIELD_LENGTH = 4; + + private static final short PROTOCOL_HEAD_LENGTH = 16; + + private final int packageLength; + private final short protocolHeadLength; + private final DeviceType shortDeviceType; + private final PackageType packageType; + private final DeviceType longDeviceType; + private final byte[] content; + + public Package(int packageLength, short protocolHeadLength, DeviceType shortDeviceType, PackageType packageType, DeviceType longDeviceType, byte[] content) { + this.packageLength = packageLength; + this.protocolHeadLength = protocolHeadLength; + this.shortDeviceType = shortDeviceType; + this.packageType = packageType; + this.longDeviceType = longDeviceType; + this.content = content; + } + + public Package(PackageType packageType, byte[] content) { + this(PROTOCOL_HEAD_LENGTH + content.length, + PROTOCOL_HEAD_LENGTH, + DeviceType.ANDROID, + packageType, + DeviceType.ANDROID, + content + ); + } + + public int getPackageLength() { + return packageLength; + } + + public short getProtocolHeadLength() { + return protocolHeadLength; + } + + public DeviceType getShortDeviceType() { + return shortDeviceType; + } + + public PackageType getPackageType() { + return packageType; + } + + public DeviceType getLongDeviceType() { + return longDeviceType; + } + + public byte[] getContent() { + return content; + } + + public enum DeviceType { + ANDROID(0x00); + + private final int value; + + DeviceType(int value) { + this.value = value; + } + + public static DeviceType valueOf(int value) { + for (DeviceType deviceType : DeviceType.values()) { + if (deviceType.value == value) { + return deviceType; + } + } + throw new IllegalArgumentException("No matching constant for [" + value + "]"); + } + + public int getValue() { + return value; + } + } + + public enum PackageType { + HEART_BEAT(0x02), + VIEWER_COUNT(0x03), + DATA(0x05), + ENTER_ROOM(0x07), + ENTER_ROOM_SUCCESS(0x08); + + private final int value; + + PackageType(int value) { + this.value = value; + } + + public static PackageType valueOf(int value) { + for (PackageType packageType : PackageType.values()) { + if (packageType.value == value) { + return packageType; + } + } + throw new IllegalArgumentException("No matching constant for [" + value + "]"); + } + + public int getValue() { + return value; + } + } +} diff --git a/src/main/java/com/hiczp/bilibili/api/live/socket/PackageHelper.java b/src/main/java/com/hiczp/bilibili/api/live/socket/PackageHelper.java new file mode 100644 index 0000000..cf45342 --- /dev/null +++ b/src/main/java/com/hiczp/bilibili/api/live/socket/PackageHelper.java @@ -0,0 +1,22 @@ +package com.hiczp.bilibili.api.live.socket; + +import com.google.gson.Gson; +import com.hiczp.bilibili.api.live.socket.entity.EnterRoomEntity; + +public class PackageHelper { + private static final Gson GSON = new Gson(); + + public static Package createEnterRoomPackage(long roomId, long userId) { + return new Package( + Package.PackageType.ENTER_ROOM, + GSON.toJson(new EnterRoomEntity(roomId, userId)).getBytes() + ); + } + + public static Package createHeatBeatPackage() { + return new Package( + Package.PackageType.HEART_BEAT, + new byte[0] + ); + } +} diff --git a/src/main/java/com/hiczp/bilibili/api/live/socket/PackageRepository.java b/src/main/java/com/hiczp/bilibili/api/live/socket/PackageRepository.java deleted file mode 100644 index 9d1981a..0000000 --- a/src/main/java/com/hiczp/bilibili/api/live/socket/PackageRepository.java +++ /dev/null @@ -1,143 +0,0 @@ -package com.hiczp.bilibili.api.live.socket; - -import com.google.gson.Gson; -import com.hiczp.bilibili.api.live.socket.entity.EnterRoomEntity; - -import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.channels.SocketChannel; -import java.util.Arrays; - -//数据包结构说明 -//00 00 00 28 00 10 00 00 00 00 00 07 00 00 00 00 -//00 00 00 28/00 10/00 00 00 00 00 07/00 00 00 00 -//1-4 字节: 数据包长度 -//5-6 字节: 协议头长度, 固定值 0x10 -//7-8 字节: 设备类型, Android 固定为 0 -//9-12 字节: 数据包类型 -//13-16 字节: 设备类型, 同 7-8 字节 -public class PackageRepository { - //数据包总长标识占用的 bytes 长度 - static final short PACKAGE_LENGTH_BYTES_LENGTH = 4; - //协议头长度标识占用的 bytes 长度 - static final short PROTOCOL_HEAD_LENGTH_BYTES_LENGTH = 2; - //设备类型短标识 bytes - static final byte[] SHORT_DEVICE_TYPE_BYTES = {0x00, 0x00}; - //数据包类型标识 bytes - static final short PACKAGE_TYPE_BYTES_LENGTH = 4; - static final byte[] HEART_BEAT_PACKAGE_TYPE_BYTES = {0x00, 0x00, 0x00, 0x02}; //心跳包 - static final byte[] VIEWER_COUNT_PACKAGE_TYPE_BYTES = {0x00, 0x00, 0x00, 0x03}; //观众人数 - static final byte[] DATA_PACKAGE_TYPE_BYTES = {0x00, 0x00, 0x00, 0x05}; //弹幕, 礼物, 系统消息 etc - static final byte[] ENTER_ROOM_PACKAGE_TYPE_BYTES = {0x00, 0x00, 0x00, 0x07}; //进入房间 - static final byte[] ENTER_ROOM_SUCCESS_PACKAGE_TYPE_BYTES = {0x00, 0x00, 0x00, 0x08}; //进入房间的响应包 - //设备类型长标识 bytes - static final byte[] LONG_DEVICE_TYPE_BYTES = {0x00, 0x00, 0x00, 0x00}; - //协议头总长度 - static final short PROTOCOL_HEAD_LENGTH = (short) (PACKAGE_LENGTH_BYTES_LENGTH + PROTOCOL_HEAD_LENGTH_BYTES_LENGTH + SHORT_DEVICE_TYPE_BYTES.length + PACKAGE_TYPE_BYTES_LENGTH + LONG_DEVICE_TYPE_BYTES.length); - //协议头长度标识 bytes - static final byte[] PROTOCOL_HEAD_LENGTH_BYTES = ByteBuffer.allocate(PROTOCOL_HEAD_LENGTH_BYTES_LENGTH).putShort(PROTOCOL_HEAD_LENGTH).array(); - private static final Gson GSON = new Gson(); - - private static ByteBuffer createPackage(byte[] packageTypeBytes, String content) { - int totalLength = PROTOCOL_HEAD_LENGTH + content.length(); - ByteBuffer byteBuffer = ByteBuffer.allocate(totalLength) - .putInt(totalLength) - .put(PROTOCOL_HEAD_LENGTH_BYTES) - .put(SHORT_DEVICE_TYPE_BYTES) - .put(packageTypeBytes) - .put(LONG_DEVICE_TYPE_BYTES) - .put(content.getBytes()); - byteBuffer.flip(); - return byteBuffer; - } - - private static void readDataFromSocketChannel(SocketChannel socketChannel, ByteBuffer byteBuffer) throws IOException { - while (byteBuffer.hasRemaining()) { - socketChannel.read(byteBuffer); - } - } - - //userId 为 0 表示用户未登录, 并不影响弹幕推送, 但是可能和用户统计有关 - public static ByteBuffer createEnterRoomPackage(int roomId, int userId) { - return createPackage(ENTER_ROOM_PACKAGE_TYPE_BYTES, GSON.toJson(new EnterRoomEntity(roomId, userId))); - } - - public static ByteBuffer createHeartBeatPackage(int roomId, int userId) { - return createPackage(HEART_BEAT_PACKAGE_TYPE_BYTES, GSON.toJson(new EnterRoomEntity(roomId, userId))); - } - - public static ByteBuffer readNextPackage(SocketChannel socketChannel) throws IOException { - //获取数据包总长度 - ByteBuffer packageLengthByteBuffer = ByteBuffer.allocate(PACKAGE_LENGTH_BYTES_LENGTH); - readDataFromSocketChannel(socketChannel, packageLengthByteBuffer); - packageLengthByteBuffer.flip(); - int packageLength = packageLengthByteBuffer.getInt(); - packageLengthByteBuffer.rewind(); - - //获取数据包剩下的部分 - ByteBuffer restPackageByteBuffer = ByteBuffer.allocate(packageLength - PACKAGE_LENGTH_BYTES_LENGTH); - readDataFromSocketChannel(socketChannel, restPackageByteBuffer); - restPackageByteBuffer.flip(); - - //合并 ByteBuffer - ByteBuffer byteBuffer = ByteBuffer.allocate(packageLengthByteBuffer.limit() + restPackageByteBuffer.limit()); - byteBuffer.put(packageLengthByteBuffer).put(restPackageByteBuffer); - byteBuffer.flip(); - return byteBuffer; - } - - public static ByteBuffer[] readNextPackageSplit(SocketChannel socketChannel) throws IOException { - //获取数据包总长度 - ByteBuffer packageLengthByteBuffer = ByteBuffer.allocate(PACKAGE_LENGTH_BYTES_LENGTH); - readDataFromSocketChannel(socketChannel, packageLengthByteBuffer); - packageLengthByteBuffer.flip(); - int packageLength = packageLengthByteBuffer.getInt(); - packageLengthByteBuffer.rewind(); - - //获取协议头长度 - ByteBuffer protocolHeadLengthByteBuffer = ByteBuffer.allocate(PROTOCOL_HEAD_LENGTH_BYTES_LENGTH); - readDataFromSocketChannel(socketChannel, protocolHeadLengthByteBuffer); - protocolHeadLengthByteBuffer.flip(); - int protocolHeadLength = protocolHeadLengthByteBuffer.getShort(); - protocolHeadLengthByteBuffer.rewind(); - - //获取剩余的协议头 - ByteBuffer restProtocolHeadByteBuffer = ByteBuffer.allocate(protocolHeadLength - PACKAGE_LENGTH_BYTES_LENGTH - PROTOCOL_HEAD_LENGTH_BYTES_LENGTH); - readDataFromSocketChannel(socketChannel, restProtocolHeadByteBuffer); - restProtocolHeadByteBuffer.flip(); - - //得到设备类型短标识 - ByteBuffer shortDeviceTypeByteBuffer = ByteBuffer.allocate(SHORT_DEVICE_TYPE_BYTES.length); - shortDeviceTypeByteBuffer.putShort(restProtocolHeadByteBuffer.getShort()); - shortDeviceTypeByteBuffer.flip(); - - //得到数据包类型 - ByteBuffer packageTypeByteBuffer = ByteBuffer.allocate(PACKAGE_TYPE_BYTES_LENGTH); - packageTypeByteBuffer.putInt(restProtocolHeadByteBuffer.getInt()); - packageTypeByteBuffer.flip(); - - //得到设备类型长标识 - ByteBuffer longDeviceTypeByteBuffer = ByteBuffer.allocate(LONG_DEVICE_TYPE_BYTES.length); - longDeviceTypeByteBuffer.putInt(restProtocolHeadByteBuffer.getInt()); - longDeviceTypeByteBuffer.flip(); - - //获取正文 - ByteBuffer contentByteBuffer = ByteBuffer.allocate(packageLength - protocolHeadLength); - readDataFromSocketChannel(socketChannel, contentByteBuffer); - contentByteBuffer.flip(); - - //组成数组 - return new ByteBuffer[]{ - packageLengthByteBuffer, //0 - protocolHeadLengthByteBuffer, //1 - shortDeviceTypeByteBuffer, //2 - packageTypeByteBuffer, //3 - longDeviceTypeByteBuffer, //4 - contentByteBuffer //5 - }; - } - - public static boolean isNextPackageIsEnterRoomSuccessPackage(SocketChannel socketChannel) throws IOException { - return Arrays.equals(readNextPackageSplit(socketChannel)[3].array(), ENTER_ROOM_SUCCESS_PACKAGE_TYPE_BYTES); - } -} diff --git a/src/main/java/com/hiczp/bilibili/api/live/socket/Utils.java b/src/main/java/com/hiczp/bilibili/api/live/socket/Utils.java deleted file mode 100644 index f45bb34..0000000 --- a/src/main/java/com/hiczp/bilibili/api/live/socket/Utils.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.hiczp.bilibili.api.live.socket; - -import java.util.Arrays; - -public class Utils { - private static byte[][] splitBytes(byte[] bytes, int n) { - int lineCount = bytes.length % n == 0 ? bytes.length / n : bytes.length / n + 1; - byte[][] result = new byte[lineCount][]; - int to; - for (int line = 1; line <= lineCount; line++) { - if (line != lineCount) { - to = line * n; - } else { - to = bytes.length; - } - result[line - 1] = Arrays.copyOfRange(bytes, (line - 1) * n, to); - } - return result; - } - - public static void printBytes(byte[] bytes) { - byte[][] data = splitBytes(bytes, 16); - byte[] currentRow; - char c; - for (int i = 0; i < data.length; i++) { - System.out.printf("%08x ", i * 16); - currentRow = data[i]; - for (int j = 0; j < currentRow.length; j++) { - System.out.printf("%02x ", currentRow[j]); - if (j == 7) { - System.out.print(" "); - } - } - if (currentRow.length < 16) { - for (int k = 0; k < (48 - currentRow.length * 2 - (currentRow.length - 1)); k++) { - System.out.print(" "); - } - } - System.out.print(" "); - for (int j = 0; j < currentRow.length; j++) { - if (currentRow[j] < ' ') { - c = '.'; - } else { - c = (char) currentRow[j]; - } - System.out.print(c); - if (j == 7) { - System.out.print(" "); - } - } - System.out.println(); - } - } -} diff --git a/src/main/java/com/hiczp/bilibili/api/live/socket/codec/PackageDecoder.java b/src/main/java/com/hiczp/bilibili/api/live/socket/codec/PackageDecoder.java new file mode 100644 index 0000000..ec4a061 --- /dev/null +++ b/src/main/java/com/hiczp/bilibili/api/live/socket/codec/PackageDecoder.java @@ -0,0 +1,30 @@ +package com.hiczp.bilibili.api.live.socket.codec; + +import com.hiczp.bilibili.api.live.socket.Package; +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.ByteToMessageDecoder; + +import java.util.List; + +public class PackageDecoder extends ByteToMessageDecoder { + @Override + protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { + int packageLength = in.readInt(); + short protocolHeadLength = in.readShort(); + Package.DeviceType shortDeviceType = Package.DeviceType.valueOf(in.readShort()); + Package.PackageType packageType = Package.PackageType.valueOf(in.readInt()); + Package.DeviceType longDeviceType = Package.DeviceType.valueOf(in.readInt()); + byte[] content = new byte[packageLength - protocolHeadLength]; + in.readBytes(content); + + out.add(new Package( + packageLength, + protocolHeadLength, + shortDeviceType, + packageType, + longDeviceType, + content + )); + } +} diff --git a/src/main/java/com/hiczp/bilibili/api/live/socket/codec/PackageEncoder.java b/src/main/java/com/hiczp/bilibili/api/live/socket/codec/PackageEncoder.java new file mode 100644 index 0000000..d789f13 --- /dev/null +++ b/src/main/java/com/hiczp/bilibili/api/live/socket/codec/PackageEncoder.java @@ -0,0 +1,18 @@ +package com.hiczp.bilibili.api.live.socket.codec; + +import com.hiczp.bilibili.api.live.socket.Package; +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.MessageToByteEncoder; + +public class PackageEncoder extends MessageToByteEncoder { + @Override + protected void encode(ChannelHandlerContext ctx, Package msg, ByteBuf out) throws Exception { + out.writeInt(msg.getPackageLength()) + .writeShort(msg.getProtocolHeadLength()) + .writeShort(msg.getShortDeviceType().getValue()) + .writeInt(msg.getPackageType().getValue()) + .writeInt(msg.getLongDeviceType().getValue()) + .writeBytes(msg.getContent()); + } +} diff --git a/src/main/java/com/hiczp/bilibili/api/live/socket/entity/ActivityEventEntity.java b/src/main/java/com/hiczp/bilibili/api/live/socket/entity/ActivityEventEntity.java new file mode 100644 index 0000000..f1e8162 --- /dev/null +++ b/src/main/java/com/hiczp/bilibili/api/live/socket/entity/ActivityEventEntity.java @@ -0,0 +1,81 @@ +package com.hiczp.bilibili.api.live.socket.entity; + +import com.google.gson.annotations.SerializedName; + +public class ActivityEventEntity { + /** + * cmd : ACTIVITY_EVENT + * data : {"keyword":"newspring_2018","type":"cracker","limit":300000,"progress":158912} + */ + + @SerializedName("cmd") + private String cmd; + @SerializedName("data") + private Data data; + + public String getCmd() { + return cmd; + } + + public void setCmd(String cmd) { + this.cmd = cmd; + } + + public Data getData() { + return data; + } + + public void setData(Data data) { + this.data = data; + } + + public static class Data { + /** + * keyword : newspring_2018 + * type : cracker + * limit : 300000 + * progress : 158912 + */ + + @SerializedName("keyword") + private String keyword; + @SerializedName("type") + private String type; + @SerializedName("limit") + private int limit; + @SerializedName("progress") + private int progress; + + public String getKeyword() { + return keyword; + } + + public void setKeyword(String keyword) { + this.keyword = keyword; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public int getLimit() { + return limit; + } + + public void setLimit(int limit) { + this.limit = limit; + } + + public int getProgress() { + return progress; + } + + public void setProgress(int progress) { + this.progress = progress; + } + } +} diff --git a/src/main/java/com/hiczp/bilibili/api/live/socket/entity/DanMuMSGEntity.java b/src/main/java/com/hiczp/bilibili/api/live/socket/entity/DanMuMsgEntity.java similarity index 97% rename from src/main/java/com/hiczp/bilibili/api/live/socket/entity/DanMuMSGEntity.java rename to src/main/java/com/hiczp/bilibili/api/live/socket/entity/DanMuMsgEntity.java index 7f388b3..05a5b00 100644 --- a/src/main/java/com/hiczp/bilibili/api/live/socket/entity/DanMuMSGEntity.java +++ b/src/main/java/com/hiczp/bilibili/api/live/socket/entity/DanMuMsgEntity.java @@ -9,7 +9,7 @@ import java.lang.reflect.Type; import java.util.List; import java.util.Optional; -public class DanMuMSGEntity { +public class DanMuMsgEntity { private static final Gson GSON = new Gson(); private static final Type STRING_LIST_TYPE = new TypeToken>() { }.getType(); @@ -76,8 +76,8 @@ public class DanMuMSGEntity { } //得到发送者的用户 ID - public int getUserId() { - return info.get(2).getAsJsonArray().get(0).getAsInt(); + public long getUserId() { + return info.get(2).getAsJsonArray().get(0).getAsLong(); } //得到发送者的用户名 diff --git a/src/main/java/com/hiczp/bilibili/api/live/socket/entity/EnterRoomEntity.java b/src/main/java/com/hiczp/bilibili/api/live/socket/entity/EnterRoomEntity.java index 6215be6..945c2ab 100644 --- a/src/main/java/com/hiczp/bilibili/api/live/socket/entity/EnterRoomEntity.java +++ b/src/main/java/com/hiczp/bilibili/api/live/socket/entity/EnterRoomEntity.java @@ -4,28 +4,28 @@ import com.google.gson.annotations.SerializedName; public class EnterRoomEntity { @SerializedName("roomid") - private int roomId; + private long roomId; @SerializedName("uid") - private int userId; + private long userId; - public EnterRoomEntity(int roomId, int userId) { + public EnterRoomEntity(long roomId, long userId) { this.roomId = roomId; this.userId = userId; } - public int getRoomId() { + public long getRoomId() { return roomId; } - public void setRoomId(int roomId) { + public void setRoomId(long roomId) { this.roomId = roomId; } - public int getUserId() { + public long getUserId() { return userId; } - public void setUserId(int userId) { + public void setUserId(long userId) { this.userId = userId; } } diff --git a/src/main/java/com/hiczp/bilibili/api/live/socket/entity/SendGiftEntity.java b/src/main/java/com/hiczp/bilibili/api/live/socket/entity/SendGiftEntity.java index 75ab111..c9897e4 100644 --- a/src/main/java/com/hiczp/bilibili/api/live/socket/entity/SendGiftEntity.java +++ b/src/main/java/com/hiczp/bilibili/api/live/socket/entity/SendGiftEntity.java @@ -75,9 +75,9 @@ public class SendGiftEntity { @SerializedName("rcost") private int rCost; @SerializedName("uid") - private int uid; + private long uid; @SerializedName("timestamp") - private int timestamp; + private long timestamp; @SerializedName("giftId") private int giftId; @SerializedName("giftType") @@ -157,19 +157,19 @@ public class SendGiftEntity { this.rCost = rCost; } - public int getUid() { + public long getUid() { return uid; } - public void setUid(int uid) { + public void setUid(long uid) { this.uid = uid; } - public int getTimestamp() { + public long getTimestamp() { return timestamp; } - public void setTimestamp(int timestamp) { + public void setTimestamp(long timestamp) { this.timestamp = timestamp; } diff --git a/src/main/java/com/hiczp/bilibili/api/live/socket/entity/SysGiftEntity.java b/src/main/java/com/hiczp/bilibili/api/live/socket/entity/SysGiftEntity.java index b8098ca..ded016a 100644 --- a/src/main/java/com/hiczp/bilibili/api/live/socket/entity/SysGiftEntity.java +++ b/src/main/java/com/hiczp/bilibili/api/live/socket/entity/SysGiftEntity.java @@ -26,9 +26,9 @@ public class SysGiftEntity { @SerializedName("url") private String url; @SerializedName("roomid") - private int roomId; + private long roomId; @SerializedName("real_roomid") - private int realRoomId; + private long realRoomId; @SerializedName("giftId") private int giftId; @SerializedName("msgTips") @@ -74,19 +74,19 @@ public class SysGiftEntity { this.url = url; } - public int getRoomId() { + public long getRoomId() { return roomId; } - public void setRoomId(int roomId) { + public void setRoomId(long roomId) { this.roomId = roomId; } - public int getRealRoomId() { + public long getRealRoomId() { return realRoomId; } - public void setRealRoomId(int realRoomId) { + public void setRealRoomId(long realRoomId) { this.realRoomId = realRoomId; } diff --git a/src/main/java/com/hiczp/bilibili/api/live/socket/entity/SysMSGEntity.java b/src/main/java/com/hiczp/bilibili/api/live/socket/entity/SysMsgEntity.java similarity index 87% rename from src/main/java/com/hiczp/bilibili/api/live/socket/entity/SysMSGEntity.java rename to src/main/java/com/hiczp/bilibili/api/live/socket/entity/SysMsgEntity.java index fa9fe7a..e5a03cf 100644 --- a/src/main/java/com/hiczp/bilibili/api/live/socket/entity/SysMSGEntity.java +++ b/src/main/java/com/hiczp/bilibili/api/live/socket/entity/SysMsgEntity.java @@ -2,7 +2,7 @@ package com.hiczp.bilibili.api.live.socket.entity; import com.google.gson.annotations.SerializedName; -public class SysMSGEntity { +public class SysMsgEntity { /** * cmd : SYS_MSG * msg : 【瑾然-】:?在直播间:?【3939852】:?赠送 小电视一个,请前往抽奖 @@ -30,11 +30,11 @@ public class SysMSGEntity { @SerializedName("url") private String url; @SerializedName("roomid") - private int roomId; + private long roomId; @SerializedName("real_roomid") - private int realRoomId; + private long realRoomId; @SerializedName("rnd") - private int rnd; + private long rnd; @SerializedName("tv_id") private String tvId; @@ -86,27 +86,27 @@ public class SysMSGEntity { this.url = url; } - public int getRoomId() { + public long getRoomId() { return roomId; } - public void setRoomId(int roomId) { + public void setRoomId(long roomId) { this.roomId = roomId; } - public int getRealRoomId() { + public long getRealRoomId() { return realRoomId; } - public void setRealRoomId(int realRoomId) { + public void setRealRoomId(long realRoomId) { this.realRoomId = realRoomId; } - public int getRnd() { + public long getRnd() { return rnd; } - public void setRnd(int rnd) { + public void setRnd(long rnd) { this.rnd = rnd; } diff --git a/src/main/java/com/hiczp/bilibili/api/live/socket/entity/WelcomeEntity.java b/src/main/java/com/hiczp/bilibili/api/live/socket/entity/WelcomeEntity.java index 80e7598..dc98347 100644 --- a/src/main/java/com/hiczp/bilibili/api/live/socket/entity/WelcomeEntity.java +++ b/src/main/java/com/hiczp/bilibili/api/live/socket/entity/WelcomeEntity.java @@ -38,7 +38,7 @@ public class WelcomeEntity { */ @SerializedName("uid") - private int uid; + private long uid; @SerializedName("uname") private String userName; @SerializedName("is_admin") @@ -46,11 +46,11 @@ public class WelcomeEntity { @SerializedName("vip") private int vip; - public int getUid() { + public long getUid() { return uid; } - public void setUid(int uid) { + public void setUid(long uid) { this.uid = uid; } @@ -62,11 +62,11 @@ public class WelcomeEntity { this.userName = userName; } - public boolean isIsAdmin() { + public boolean isAdmin() { return isAdmin; } - public void setIsAdmin(boolean isAdmin) { + public void setAdmin(boolean isAdmin) { this.isAdmin = isAdmin; } diff --git a/src/main/java/com/hiczp/bilibili/api/live/socket/entity/WelcomeGuardEntity.java b/src/main/java/com/hiczp/bilibili/api/live/socket/entity/WelcomeGuardEntity.java index 72923e3..d8be2aa 100644 --- a/src/main/java/com/hiczp/bilibili/api/live/socket/entity/WelcomeGuardEntity.java +++ b/src/main/java/com/hiczp/bilibili/api/live/socket/entity/WelcomeGuardEntity.java @@ -14,7 +14,7 @@ public class WelcomeGuardEntity { @SerializedName("data") private DataEntity data; @SerializedName("roomid") - private int roomId; + private long roomId; public String getCmd() { return cmd; @@ -32,11 +32,11 @@ public class WelcomeGuardEntity { this.data = data; } - public int getRoomId() { + public long getRoomId() { return roomId; } - public void setRoomId(int roomId) { + public void setRoomId(long roomId) { this.roomId = roomId; } diff --git a/src/main/java/com/hiczp/bilibili/api/live/socket/event/ActivityEventPackageEvent.java b/src/main/java/com/hiczp/bilibili/api/live/socket/event/ActivityEventPackageEvent.java new file mode 100644 index 0000000..9ad2f88 --- /dev/null +++ b/src/main/java/com/hiczp/bilibili/api/live/socket/event/ActivityEventPackageEvent.java @@ -0,0 +1,18 @@ +package com.hiczp.bilibili.api.live.socket.event; + +import com.hiczp.bilibili.api.live.socket.entity.ActivityEventEntity; + +import java.util.EventObject; + +public class ActivityEventPackageEvent extends EventObject { + private ActivityEventEntity activityEventEntity; + + public ActivityEventPackageEvent(Object source, ActivityEventEntity activityEventEntity) { + super(source); + this.activityEventEntity = activityEventEntity; + } + + public ActivityEventEntity getActivityEventEntity() { + return activityEventEntity; + } +} diff --git a/src/main/java/com/hiczp/bilibili/api/live/socket/event/ConnectSucceedEvent.java b/src/main/java/com/hiczp/bilibili/api/live/socket/event/ConnectSucceedEvent.java new file mode 100644 index 0000000..2622d15 --- /dev/null +++ b/src/main/java/com/hiczp/bilibili/api/live/socket/event/ConnectSucceedEvent.java @@ -0,0 +1,9 @@ +package com.hiczp.bilibili.api.live.socket.event; + +import java.util.EventObject; + +public class ConnectSucceedEvent extends EventObject { + public ConnectSucceedEvent(Object source) { + super(source); + } +} diff --git a/src/main/java/com/hiczp/bilibili/api/live/socket/event/ConnectionCloseEvent.java b/src/main/java/com/hiczp/bilibili/api/live/socket/event/ConnectionCloseEvent.java new file mode 100644 index 0000000..1315d68 --- /dev/null +++ b/src/main/java/com/hiczp/bilibili/api/live/socket/event/ConnectionCloseEvent.java @@ -0,0 +1,9 @@ +package com.hiczp.bilibili.api.live.socket.event; + +import java.util.EventObject; + +public class ConnectionCloseEvent extends EventObject { + public ConnectionCloseEvent(Object source) { + super(source); + } +} diff --git a/src/main/java/com/hiczp/bilibili/api/live/socket/event/DanMuMsgPackageEvent.java b/src/main/java/com/hiczp/bilibili/api/live/socket/event/DanMuMsgPackageEvent.java new file mode 100644 index 0000000..f3ce1d5 --- /dev/null +++ b/src/main/java/com/hiczp/bilibili/api/live/socket/event/DanMuMsgPackageEvent.java @@ -0,0 +1,18 @@ +package com.hiczp.bilibili.api.live.socket.event; + +import com.hiczp.bilibili.api.live.socket.entity.DanMuMsgEntity; + +import java.util.EventObject; + +public class DanMuMsgPackageEvent extends EventObject { + private DanMuMsgEntity danMuMsgEntity; + + public DanMuMsgPackageEvent(Object source, DanMuMsgEntity danMuMsgEntity) { + super(source); + this.danMuMsgEntity = danMuMsgEntity; + } + + public DanMuMsgEntity getDanMuMsgEntity() { + return danMuMsgEntity; + } +} diff --git a/src/main/java/com/hiczp/bilibili/api/live/socket/event/LivePackageEvent.java b/src/main/java/com/hiczp/bilibili/api/live/socket/event/LivePackageEvent.java new file mode 100644 index 0000000..1d54a0f --- /dev/null +++ b/src/main/java/com/hiczp/bilibili/api/live/socket/event/LivePackageEvent.java @@ -0,0 +1,18 @@ +package com.hiczp.bilibili.api.live.socket.event; + +import com.hiczp.bilibili.api.live.socket.entity.LiveEntity; + +import java.util.EventObject; + +public class LivePackageEvent extends EventObject { + private LiveEntity liveEntity; + + public LivePackageEvent(Object source, LiveEntity liveEntity) { + super(source); + this.liveEntity = liveEntity; + } + + public LiveEntity getLiveEntity() { + return liveEntity; + } +} diff --git a/src/main/java/com/hiczp/bilibili/api/live/socket/event/PreparingPackageEvent.java b/src/main/java/com/hiczp/bilibili/api/live/socket/event/PreparingPackageEvent.java new file mode 100644 index 0000000..9267736 --- /dev/null +++ b/src/main/java/com/hiczp/bilibili/api/live/socket/event/PreparingPackageEvent.java @@ -0,0 +1,18 @@ +package com.hiczp.bilibili.api.live.socket.event; + +import com.hiczp.bilibili.api.live.socket.entity.PreparingEntity; + +import java.util.EventObject; + +public class PreparingPackageEvent extends EventObject { + private PreparingEntity preparingEntity; + + public PreparingPackageEvent(Object source, PreparingEntity preparingEntity) { + super(source); + this.preparingEntity = preparingEntity; + } + + public PreparingEntity getPreparingEntity() { + return preparingEntity; + } +} diff --git a/src/main/java/com/hiczp/bilibili/api/live/socket/event/SendGiftPackageEvent.java b/src/main/java/com/hiczp/bilibili/api/live/socket/event/SendGiftPackageEvent.java new file mode 100644 index 0000000..96a7f08 --- /dev/null +++ b/src/main/java/com/hiczp/bilibili/api/live/socket/event/SendGiftPackageEvent.java @@ -0,0 +1,18 @@ +package com.hiczp.bilibili.api.live.socket.event; + +import com.hiczp.bilibili.api.live.socket.entity.SendGiftEntity; + +import java.util.EventObject; + +public class SendGiftPackageEvent extends EventObject { + private SendGiftEntity sendGiftEntity; + + public SendGiftPackageEvent(Object source, SendGiftEntity sendGiftEntity) { + super(source); + this.sendGiftEntity = sendGiftEntity; + } + + public SendGiftEntity getSendGiftEntity() { + return sendGiftEntity; + } +} diff --git a/src/main/java/com/hiczp/bilibili/api/live/socket/event/SysGiftPackageEvent.java b/src/main/java/com/hiczp/bilibili/api/live/socket/event/SysGiftPackageEvent.java new file mode 100644 index 0000000..9f6bbe5 --- /dev/null +++ b/src/main/java/com/hiczp/bilibili/api/live/socket/event/SysGiftPackageEvent.java @@ -0,0 +1,18 @@ +package com.hiczp.bilibili.api.live.socket.event; + +import com.hiczp.bilibili.api.live.socket.entity.SysGiftEntity; + +import java.util.EventObject; + +public class SysGiftPackageEvent extends EventObject { + private SysGiftEntity sysGiftEntity; + + public SysGiftPackageEvent(Object source, SysGiftEntity sysGiftEntity) { + super(source); + this.sysGiftEntity = sysGiftEntity; + } + + public SysGiftEntity getSysGiftEntity() { + return sysGiftEntity; + } +} diff --git a/src/main/java/com/hiczp/bilibili/api/live/socket/event/SysMsgPackageEvent.java b/src/main/java/com/hiczp/bilibili/api/live/socket/event/SysMsgPackageEvent.java new file mode 100644 index 0000000..c24980d --- /dev/null +++ b/src/main/java/com/hiczp/bilibili/api/live/socket/event/SysMsgPackageEvent.java @@ -0,0 +1,18 @@ +package com.hiczp.bilibili.api.live.socket.event; + +import com.hiczp.bilibili.api.live.socket.entity.SysMsgEntity; + +import java.util.EventObject; + +public class SysMsgPackageEvent extends EventObject { + private SysMsgEntity sysMsgEntity; + + public SysMsgPackageEvent(Object source, SysMsgEntity sysMsgEntity) { + super(source); + this.sysMsgEntity = sysMsgEntity; + } + + public SysMsgEntity getSysMsgEntity() { + return sysMsgEntity; + } +} diff --git a/src/main/java/com/hiczp/bilibili/api/live/socket/event/UnknownPackageEvent.java b/src/main/java/com/hiczp/bilibili/api/live/socket/event/UnknownPackageEvent.java new file mode 100644 index 0000000..c13ba13 --- /dev/null +++ b/src/main/java/com/hiczp/bilibili/api/live/socket/event/UnknownPackageEvent.java @@ -0,0 +1,16 @@ +package com.hiczp.bilibili.api.live.socket.event; + +import java.util.EventObject; + +public class UnknownPackageEvent extends EventObject { + private String json; + + public UnknownPackageEvent(Object source, String json) { + super(source); + this.json = json; + } + + public String getJson() { + return json; + } +} diff --git a/src/main/java/com/hiczp/bilibili/api/live/socket/event/ViewerCountPackageEvent.java b/src/main/java/com/hiczp/bilibili/api/live/socket/event/ViewerCountPackageEvent.java new file mode 100644 index 0000000..44e960f --- /dev/null +++ b/src/main/java/com/hiczp/bilibili/api/live/socket/event/ViewerCountPackageEvent.java @@ -0,0 +1,16 @@ +package com.hiczp.bilibili.api.live.socket.event; + +import java.util.EventObject; + +public class ViewerCountPackageEvent extends EventObject { + private int viewerCount; + + public ViewerCountPackageEvent(Object source, int viewerCount) { + super(source); + this.viewerCount = viewerCount; + } + + public int getViewerCount() { + return viewerCount; + } +} diff --git a/src/main/java/com/hiczp/bilibili/api/live/socket/event/WelcomeGuardPackageEvent.java b/src/main/java/com/hiczp/bilibili/api/live/socket/event/WelcomeGuardPackageEvent.java new file mode 100644 index 0000000..9916726 --- /dev/null +++ b/src/main/java/com/hiczp/bilibili/api/live/socket/event/WelcomeGuardPackageEvent.java @@ -0,0 +1,18 @@ +package com.hiczp.bilibili.api.live.socket.event; + +import com.hiczp.bilibili.api.live.socket.entity.WelcomeGuardEntity; + +import java.util.EventObject; + +public class WelcomeGuardPackageEvent extends EventObject { + private WelcomeGuardEntity welcomeGuardEntity; + + public WelcomeGuardPackageEvent(Object source, WelcomeGuardEntity welcomeGuardEntity) { + super(source); + this.welcomeGuardEntity = welcomeGuardEntity; + } + + public WelcomeGuardEntity getWelcomeGuardEntity() { + return welcomeGuardEntity; + } +} diff --git a/src/main/java/com/hiczp/bilibili/api/live/socket/event/WelcomePackageEvent.java b/src/main/java/com/hiczp/bilibili/api/live/socket/event/WelcomePackageEvent.java new file mode 100644 index 0000000..d9acf05 --- /dev/null +++ b/src/main/java/com/hiczp/bilibili/api/live/socket/event/WelcomePackageEvent.java @@ -0,0 +1,18 @@ +package com.hiczp.bilibili.api.live.socket.event; + +import com.hiczp.bilibili.api.live.socket.entity.WelcomeEntity; + +import java.util.EventObject; + +public class WelcomePackageEvent extends EventObject { + private WelcomeEntity welcomeEntity; + + public WelcomePackageEvent(Object source, WelcomeEntity welcomeEntity) { + super(source); + this.welcomeEntity = welcomeEntity; + } + + public WelcomeEntity getWelcomeEntity() { + return welcomeEntity; + } +} diff --git a/src/main/java/com/hiczp/bilibili/api/live/socket/handler/LiveClientHandler.java b/src/main/java/com/hiczp/bilibili/api/live/socket/handler/LiveClientHandler.java new file mode 100644 index 0000000..cf39b56 --- /dev/null +++ b/src/main/java/com/hiczp/bilibili/api/live/socket/handler/LiveClientHandler.java @@ -0,0 +1,146 @@ +package com.hiczp.bilibili.api.live.socket.handler; + +import com.google.common.eventbus.EventBus; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonParseException; +import com.google.gson.JsonParser; +import com.hiczp.bilibili.api.live.socket.Package; +import com.hiczp.bilibili.api.live.socket.PackageHelper; +import com.hiczp.bilibili.api.live.socket.entity.*; +import com.hiczp.bilibili.api.live.socket.event.*; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.handler.timeout.IdleState; +import io.netty.handler.timeout.IdleStateEvent; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.nio.ByteBuffer; +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; + +public class LiveClientHandler extends SimpleChannelInboundHandler { + private static final Logger LOGGER = LoggerFactory.getLogger(LiveClientHandler.class); + private static final Gson GSON = new Gson(); + private static final JsonParser JSON_PARSER = new JsonParser(); + + private long roomId; + private long userId; + private EventBus eventBus; + + public LiveClientHandler(long roomId, long userId, EventBus eventBus) { + this.roomId = roomId; + this.userId = userId; + this.eventBus = eventBus; + } + + @Override + public void channelActive(ChannelHandlerContext ctx) throws Exception { + super.channelActive(ctx); + LOGGER.debug("Sending Enter Room package"); + ctx.writeAndFlush(PackageHelper.createEnterRoomPackage(roomId, userId)); + } + + @Override + public void channelInactive(ChannelHandlerContext ctx) throws Exception { + super.channelInactive(ctx); + eventBus.post(new ConnectionCloseEvent(this)); + } + + @Override + public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { + super.userEventTriggered(ctx, evt); + if (evt instanceof IdleStateEvent) { + IdleStateEvent idleStateEvent = (IdleStateEvent) evt; + if (idleStateEvent.state() == IdleState.READER_IDLE) { + ctx.close(); + } + } + } + + @Override + protected void channelRead0(ChannelHandlerContext ctx, Package msg) throws Exception { + switch (msg.getPackageType()) { + case VIEWER_COUNT: { + eventBus.post(new ViewerCountPackageEvent(this, ByteBuffer.wrap(msg.getContent()).getInt())); + } + break; + case ENTER_ROOM_SUCCESS: { + eventBus.post(new ConnectSucceedEvent(this)); + ctx.executor().scheduleAtFixedRate( + () -> ctx.writeAndFlush(PackageHelper.createHeatBeatPackage()), + 0L, + 30L, + TimeUnit.SECONDS + ); + } + break; + case DATA: { + String content = new String(msg.getContent()); + String cmd = JSON_PARSER.parse(content) + .getAsJsonObject() + .get("cmd") + .getAsString(); + Supplier eventCreationExpression; //try 不能写在 switch 外面, 用 lambda 来延迟执行 + switch (cmd) { + case "DANMU_MSG": { + eventCreationExpression = () -> new DanMuMsgPackageEvent(this, GSON.fromJson(content, DanMuMsgEntity.class)); + } + break; + case "SEND_GIFT": { + eventCreationExpression = () -> new SendGiftPackageEvent(this, GSON.fromJson(content, SendGiftEntity.class)); + } + break; + case "SYS_GIFT": { + eventCreationExpression = () -> new SysGiftPackageEvent(this, GSON.fromJson(content, SysGiftEntity.class)); + } + break; + case "SYS_MSG": { + eventCreationExpression = () -> new SysMsgPackageEvent(this, GSON.fromJson(content, SysMsgEntity.class)); + } + break; + case "WELCOME": { + eventCreationExpression = () -> new WelcomePackageEvent(this, GSON.fromJson(content, WelcomeEntity.class)); + } + break; + case "WELCOME_GUARD": { + eventCreationExpression = () -> new WelcomeGuardPackageEvent(this, GSON.fromJson(content, WelcomeGuardEntity.class)); + } + break; + case "LIVE": { + eventCreationExpression = () -> new LivePackageEvent(this, GSON.fromJson(content, LiveEntity.class)); + } + break; + case "PREPARING": { + eventCreationExpression = () -> new PreparingPackageEvent(this, GSON.fromJson(content, PreparingEntity.class)); + } + break; + case "ACTIVITY_EVENT": { + eventCreationExpression = () -> new ActivityEventPackageEvent(this, GSON.fromJson(content, ActivityEventEntity.class)); + } + break; + default: { + LOGGER.error("Received unknown json below: \n{}", formatJson(content)); + eventCreationExpression = () -> new UnknownPackageEvent(this, content); + } + break; + } + + try { + eventBus.post(eventCreationExpression.get()); + } catch (JsonParseException e) { + LOGGER.error("Json parse error: {}, json below: \n{}", e.getMessage(), formatJson(content)); + } + } + break; + } + } + + private String formatJson(String json) { + return new GsonBuilder() + .setPrettyPrinting() + .create() + .toJson(JSON_PARSER.parse(json)); + } +} diff --git a/src/test/java/com/hiczp/bilibili/api/test/LiveClientTest.java b/src/test/java/com/hiczp/bilibili/api/test/LiveClientTest.java new file mode 100644 index 0000000..4af954a --- /dev/null +++ b/src/test/java/com/hiczp/bilibili/api/test/LiveClientTest.java @@ -0,0 +1,178 @@ +package com.hiczp.bilibili.api.test; + +import com.google.common.eventbus.Subscribe; +import com.hiczp.bilibili.api.BilibiliAPI; +import com.hiczp.bilibili.api.live.socket.LiveClient; +import com.hiczp.bilibili.api.live.socket.entity.*; +import com.hiczp.bilibili.api.live.socket.event.*; +import org.junit.FixMethodOrder; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runners.MethodSorters; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; + +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +public class LiveClientTest { + private static final Logger LOGGER = LoggerFactory.getLogger(LiveClientTest.class); + private static final BilibiliAPI BILIBILI_API = Config.getBilibiliAPI(); + private static final int ROOM_ID = 3; + private static final long TEST_TIME = 70 * 1000; + + @Ignore + @Test + public void _0_duplicateConnectAndCloseTest() throws Exception { + LiveClient liveClient = BILIBILI_API + .getLiveClient(ROOM_ID) + .setReconnectLimit(5) + .setReconnectDelay(1); + LOGGER.debug("Connecting!"); + liveClient.connect(); + Thread.sleep(5000); + LOGGER.debug("Connecting!"); + liveClient.connect(); + Thread.sleep(5000); + LOGGER.debug("Disconnecting!"); + liveClient.close(); + Thread.sleep(5000); + LOGGER.debug("Disconnecting!"); + liveClient.close(); + Thread.sleep(5000); + LOGGER.debug("Connecting!"); + liveClient.connect(); + Thread.sleep(5000); + LOGGER.debug("Disconnecting!"); + liveClient.close(); + Thread.sleep(5000); + } + + @Test + public void _1_longTimeTest() throws Exception { + LiveClient liveClient = BILIBILI_API + .getLiveClient(ROOM_ID) + .setReconnectLimit(5) + .setReconnectDelay(1) + .registerListener(new Listener()); + LOGGER.debug("Start long-time test"); + LOGGER.debug("Connecting!"); + liveClient.connect(); + Thread.sleep(TEST_TIME); + LOGGER.debug("Disconnecting!"); + liveClient.close(); + Thread.sleep(5000); + } + + private class Listener { + private final Logger LOGGER = LoggerFactory.getLogger(Listener.class); + + @Subscribe + public void activityEvent(ActivityEventPackageEvent activityEventPackageEvent) { + ActivityEventEntity.Data data = activityEventPackageEvent.getActivityEventEntity().getData(); + LOGGER.info("[ActivityEvent] keyword: {}, type: {}, progress: {}%", + data.getKeyword(), + data.getType(), + ((float) data.getProgress() / data.getLimit()) * 100 + ); + } + + @Subscribe + public void connectionClose(ConnectionCloseEvent connectionCloseEvent) { + LOGGER.info("[ConnectionClose] Connection closed"); + } + + @Subscribe + public void connectSucceed(ConnectSucceedEvent connectSucceedEvent) { + LOGGER.info("[ConnectSucceed] Connect succeed"); + } + + @Subscribe + public void danMuMsg(DanMuMsgPackageEvent danMuMsgPackageEvent) { + DanMuMsgEntity danMuMsgEntity = danMuMsgPackageEvent.getDanMuMsgEntity(); + StringBuilder stringBuilder = new StringBuilder("[DanMuMsg] "); + + danMuMsgEntity.getFansMedalName().ifPresent(fansMedalName -> + stringBuilder.append(String.format("[%s %d] ", fansMedalName, danMuMsgEntity.getFansMedalLevel().get())) + ); + + List userTitles = danMuMsgEntity.getUserTitles(); + if (!userTitles.isEmpty()) { + stringBuilder.append(userTitles.get(0)) + .append(" "); + } + + stringBuilder.append(String.format("[UL %d] ", danMuMsgEntity.getUserLevel())); + + stringBuilder.append(String.format("%s: ", danMuMsgEntity.getUsername())); + + stringBuilder.append(danMuMsgEntity.getMessage()); + + LOGGER.info(stringBuilder.toString()); + } + + @Subscribe + public void live(LivePackageEvent livePackageEvent) { + LOGGER.info("[Live] Room {} start live", livePackageEvent.getLiveEntity().getRoomId()); + } + + @Subscribe + public void preparing(PreparingPackageEvent preparingPackageEvent) { + LOGGER.info("[Preparing] Room {} stop live", preparingPackageEvent.getPreparingEntity().getRoomId()); + } + + @Subscribe + public void sendGift(SendGiftPackageEvent sendGiftPackageEvent) { + SendGiftEntity.DataEntity dataEntity = sendGiftPackageEvent.getSendGiftEntity().getData(); + LOGGER.info("[SendGift] {} give {}*{}", + dataEntity.getUserName(), + dataEntity.getGiftName(), + dataEntity.getNum() + ); + } + + @Subscribe + public void SysGift(SysGiftPackageEvent sysGiftPackageEvent) { + SysGiftEntity sysGiftEntity = sysGiftPackageEvent.getSysGiftEntity(); + LOGGER.info("[SysGift] {}: {}", + sysGiftEntity.getMsg(), + sysGiftEntity.getUrl() + ); + } + + @Subscribe + public void SysMsg(SysMsgPackageEvent sysMsgPackageEvent) { + SysMsgEntity sysMsgEntity = sysMsgPackageEvent.getSysMsgEntity(); + LOGGER.info("[SysMsg] {}: {}", + sysMsgEntity.getMsg(), + sysMsgEntity.getUrl() + ); + } + + @Subscribe + public void ViewerCount(ViewerCountPackageEvent viewerCountPackageEvent) { + LOGGER.info("[ViewerCount] {}", viewerCountPackageEvent.getViewerCount()); + } + + @Subscribe + public void WelcomeGuard(WelcomeGuardPackageEvent welcomeGuardPackageEvent) { + WelcomeGuardEntity.DataEntity dataEntity = welcomeGuardPackageEvent.getWelcomeGuardEntity().getData(); + LOGGER.info("[WelcomeGuard] [GL {}] {}", + dataEntity.getGuardLevel(), + dataEntity.getUsername() + ); + } + + @Subscribe + public void Welcome(WelcomePackageEvent welcomePackageEvent) { + WelcomeEntity.DataEntity dataEntity = welcomePackageEvent.getWelcomeEntity().getData(); + StringBuilder stringBuilder = new StringBuilder("[Welcome] "); + if (dataEntity.isAdmin()) { + stringBuilder.append("[ADMIN] "); + } + stringBuilder.append(String.format("[VIP %d] ", dataEntity.getVip())) + .append(dataEntity.getUserName()); + LOGGER.info(stringBuilder.toString()); + } + } +} diff --git a/src/test/java/com/hiczp/bilibili/api/test/RuleSuite.java b/src/test/java/com/hiczp/bilibili/api/test/RuleSuite.java index de12508..d54b1df 100644 --- a/src/test/java/com/hiczp/bilibili/api/test/RuleSuite.java +++ b/src/test/java/com/hiczp/bilibili/api/test/RuleSuite.java @@ -14,7 +14,8 @@ import java.io.InputStreamReader; @RunWith(Suite.class) @Suite.SuiteClasses({ - UserInfoTest.class + UserInfoTest.class, + LiveClientTest.class }) public class RuleSuite { @ClassRule diff --git a/src/test/java/com/hiczp/bilibili/api/test/UserInfoTest.java b/src/test/java/com/hiczp/bilibili/api/test/UserInfoTest.java index 300a276..c382375 100644 --- a/src/test/java/com/hiczp/bilibili/api/test/UserInfoTest.java +++ b/src/test/java/com/hiczp/bilibili/api/test/UserInfoTest.java @@ -11,13 +11,12 @@ import org.slf4j.LoggerFactory; public class UserInfoTest { private static final Logger LOGGER = LoggerFactory.getLogger(UserInfoTest.class); private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create(); - - private final BilibiliAPI bilibiliAPI = Config.getBilibiliAPI(); + private static final BilibiliAPI BILIBILI_API = Config.getBilibiliAPI(); @Test public void getUserInfo() throws Exception { - InfoEntity infoEntity = bilibiliAPI.getPassportService() - .getInfo(bilibiliAPI.getBilibiliAccount().getAccessToken()) + InfoEntity infoEntity = BILIBILI_API.getPassportService() + .getInfo(BILIBILI_API.getBilibiliAccount().getAccessToken()) .execute() .body(); LOGGER.debug("UserInfo below: \n{}", GSON.toJson(infoEntity));