package com.yutou.bilibili.BiliBili; import com.yutou.bilibili.BiliBili.Datas.GiftData; import com.yutou.bilibili.BiliBili.Datas.LiveData; import com.alibaba.fastjson.JSONObject; import com.yutou.bilibili.BiliBili.Services.IBiliBiliLiveService; import com.yutou.bilibili.BiliBili.Tools.SaveLive; import com.yutou.bilibili.Tools.AppTools; import com.yutou.bilibili.mybatis.Bili.mybatis.model.BilibiliLiveData; import com.yutou.bilibili.mybatis.Bili.mybatis.model.BilibiliLiveInfo; import com.yutou.bilibili.mybatis.Bili.mybatis.model.BilibiliUpInfo; import com.yutou.bilibili.sqlite.BiliBiliLiveDatabasesManager; import org.java_websocket.client.WebSocketClient; import org.java_websocket.handshake.ServerHandshake; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; import javax.annotation.Resource; import java.io.ByteArrayOutputStream; import java.io.File; import java.net.URI; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.text.SimpleDateFormat; import java.util.*; public class Live implements ApplicationContextAware { IBiliBiliLiveService service; public static List lives = new ArrayList<>(); private int roomId; private boolean isLogin; private boolean run; private int userId; private WebSocketClient client; private int popular; private BilibiliLiveInfo info; private BilibiliUpInfo upData; private Timer heartBeattimer; private String danmuFileName; @PostConstruct public void init() { } public Live() { if (applicationContext != null) service = getBean(IBiliBiliLiveService.class); info = new BilibiliLiveInfo(); info.setGiftuser(0); info.setPopular(0); info.setUserindex(0); info.setVipuserindex(0); } /** * 构造方法 * * @param roomId 直播间roomId,不是房间号,有可能是临时房间号,得通过 {@link com.yutou.bilibili.BiliBili.Tools.BiliTools#getBiliUpInfo(String)} 来获取roomId * @param isLogin 是否登录用户 */ public void add(int roomId, boolean isLogin) { this.roomId = roomId; this.isLogin = isLogin; this.danmuFileName = "[" + AppTools.getToDayTime() + "]_" + roomId + "_" + System.currentTimeMillis(); upData = new BilibiliUpInfo(); upData.setRoomid(roomId); info.setRoomid(roomId); info.setGiftuser(0); info.setVipuserindex(0); info.setUserindex(0); Live.lives.add(this); updateUpInfo(); com.yutou.bilibili.Tools.Log.i("roomId = " + roomId + ", isLogin = " + isLogin); } private void updateUpInfo() { try { upData = service.queryUp(upData); upData.setLive(1); } catch (Exception e) { e.printStackTrace(); } } private int reloadLike = 0; /** * 开始监听 * * @throws Exception 发生异常 */ public void start() throws Exception { run = true; if (LiveUtils.isLivePlayer(roomId)) { upData.setLive(1); } String url = LiveUtils.getLiveUrl(roomId); if(url==null){ stop(); return; } HashMap header = new HashMap(); header.put("Sec-WebSocket-Extensions", "permessage-deflate; client_max_window_bits"); header.put("Sec-WebSocket-Key", "tORCZd8AI6xIyvqvgvI1Vw=="); header.put("Sec-WebSocket-Version", "13"); header.put("Cache-Control", "no-cache"); header.put("Connection", "Upgrade"); header.put("Host", "tx-gz-live-comet-02.chat.bilibili.com"); header.put("Origin", "https://live.bilibili.com"); header.put("Pragma", "no-cache"); header.put("Upgrade", "websocket"); header.put("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36"); client = new WebSocketClient(new URI(url), header) { private long time = 0; private boolean init = true; @Override public void onOpen(ServerHandshake serverHandshake) { time = System.currentTimeMillis(); com.yutou.bilibili.Tools.Log.i("ws: ok"); try { likeLive(); } catch (Exception e) { e.printStackTrace(); } } @Override public void onMessage(String s) { } @Override public void onMessage(ByteBuffer bytes) { super.onMessage(bytes); if (init) { heartBeattimer = new Timer(); heartBeattimer.schedule(new sendHeartbeat(), 2000, 20000); init = false; } decompress(bytes.array()); } @Override public void onClose(int i, String s, boolean b) { com.yutou.bilibili.Tools.Log.i("连接时间:" + (System.currentTimeMillis() - time)); if (upData.getOfflinelistening() == 1) { System.err.println(roomId + " 断开连接,重连..."); try { init = true; heartBeattimer.cancel(); client.close(); start(); } catch (Exception e) { e.printStackTrace(); } return; } stop(); client.close(); run = false; throw new RuntimeException("连接关闭: code = " + i + " msg = " + s + " b = " + b + " roomId=" + roomId); } @Override public void onClosing(int code, String reason, boolean remote) { super.onClosing(code, reason, remote); com.yutou.bilibili.Tools.Log.i("code = " + code + ", reason = " + reason + ", remote = " + remote); } @Override public void onError(Exception e) { e.printStackTrace(); run = false; client.close(); } }; client.connect(); } public void stop() { run = false; client.close(); if (SaveLive.getInstance().checkLive(roomId)) { SaveLive.getInstance().stop(roomId); } Live.lives.remove(this); com.yutou.bilibili.Tools.Log.i("退出"+roomId+"直播间"); } /** * 连接房间 * * @throws Exception e */ private void likeLive() throws Exception { JSONObject tmp = LiveUtils.http_get("https://api.bilibili.com/x/web-interface/nav"); if (tmp.getInteger("code") == -101) { if (isLogin) { new File("cookies.json").deleteOnExit(); upData.setLive(0); service.updateUpInfo(upData); stop(); return; } else { userId = 0; } } else if (tmp.getInteger("code") == 0) { userId = tmp.getJSONObject("data").getInteger("mid"); } JSONObject http = LiveUtils.http_get("https://api.live.bilibili.com/xlive/web-room/v1/index/getDanmuInfo?id=" + roomId + "&type=0"); JSONObject json = new JSONObject(); json.put("uid", userId); json.put("roomid", roomId); json.put("protover", 2); json.put("platform", "web"); json.put("clientver", "2.6.36"); json.put("type", 2); json.put("key", http.getJSONObject("data").getString("token")); com.yutou.bilibili.Tools.Log.i(json.toJSONString()); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); outputStream.write(LiveUtils.toLH(json.toJSONString().length() + 16)); outputStream.write(new byte[]{0, 16, 0, 1, 0, 0, 0, 7, 0, 0, 0, 1}); outputStream.write(json.toJSONString().getBytes(StandardCharsets.UTF_8)); outputStream.flush(); // LiveUtils.printHex(outputStream.toByteArray()); client.send(outputStream.toByteArray()); } /** * 解压缩 * * @param data 待压缩的数据 */ public void decompress(byte[] data) { try { byte[] bytes = new byte[data.length - 16]; System.arraycopy(data, 16, bytes, 0, data.length - 16); if (data.length > 32) { List list = LiveUtils.getMsgList(LiveUtils.dec(bytes), new ArrayList<>(), true); for (String str : list) { processData(str, data); } } else { try { JSONObject json = JSONObject.parseObject(new String(bytes, StandardCharsets.UTF_8)); com.yutou.bilibili.Tools.Log.i(json.toJSONString()); } catch (Exception e) { popular = LiveUtils.bytesToInt2(bytes, 0); info.setPopular(popular); } } } catch (Exception e) { e.printStackTrace(); com.yutou.bilibili.Tools.Log.i("----------ERROR----------"); com.yutou.bilibili.Tools.Log.i(new String(data, StandardCharsets.UTF_8)); LiveUtils.printHex(LiveUtils.dec(data)); com.yutou.bilibili.Tools.Log.i(new SimpleDateFormat("HH:mm:ss.SSS").format(new Date())); } } private void checkLive() { boolean isLive = LiveUtils.isLivePlayer(roomId); upData.setLive(isLive ? 1 : 0); if (isLive && upData.getSavelive() == 1) { SaveLive.getInstance().addLive(this); } } public BiliBiliLiveDatabasesManager danmuManager; /** * 处理数据 * * @param msg 数据json * @param bytes 原始hex数据 */ public void processData(String msg, byte[] bytes) { try { JSONObject json = JSONObject.parseObject(msg); JSONObject data; BilibiliLiveData liveData = new BilibiliLiveData(); String danmu = null; GiftData giftData; switch (json.getString("cmd")) { case "INTERACT_WORD"://普通用户进直播间 // com.yutou.bilibili.Tools.Log.i(json.getJSONObject("data").getString("uname") + " 进入到直播间"); danmu = json.getJSONObject("data").getString("uname") + " 进入到直播间"; liveData.setUid(json.getJSONObject("data").getInteger("uid")); liveData.setType(LiveData.INTERACT_WORD); liveData.setMsg(danmu); liveData.setRoomid(roomId); liveData.setSubtime(new Date()); service.addLiveData(liveData); break; case "DANMU_MSG"://普通弹幕 danmu = json.getJSONArray("info").getJSONArray(2).getString(1) + ":" + json.getJSONArray("info").getString(1); liveData.setUid(json.getJSONArray("info").getJSONArray(2).getInteger(0)); liveData.setType(LiveData.DANMU_MSG); liveData.setMsg(danmu); liveData.setRoomid(roomId); liveData.setSubtime(new Date()); if (upData != null && upData.getSavedanmu() == 1) { // service.addLiveData(liveData); /* if(danmuManager==null){ danmuManager=BiliBiliLiveDatabasesManager.getInstance(); danmuManager.init(danmuFileName); } danmuManager.addLiveData(liveData);*/ } // com.yutou.bilibili.Tools.Log.i(json.toJSONString()); break; case "SEND_GIFT"://送礼 data = json.getJSONObject("data"); giftData = getGiftData(data.getInteger("giftId")); if (giftData == null) { giftData = new GiftData(); giftData.setName(data.getString("giftName")); giftData.setId(data.getInteger("giftId")); giftData.setPrice(data.getInteger("price")); giftData.setIcon(""); giftData.setDesc("这是阿B没有收录的礼物,金额可能不准(无法判别为免费礼物)"); giftData.setRights("?"); } danmu = data.getString("uname") + " " + data.getString("action") + " " + giftData.getName(); liveData.setType(LiveData.SEND_GIFT); liveData.setUid(data.getInteger("uid")); liveData.setGiftid(giftData.getId()); liveData.setGiftindex(data.getInteger("num")); liveData.setGiftname(giftData.getName()); liveData.setMsg(danmu); liveData.setPrice(giftData.getPrice()); liveData.setPriceofcommission(giftData.getPrice() == 0 ? 0 : giftData.getPrice() / 2); liveData.setSubtime(new Date()); liveData.setRoomid(roomId); info.setGiftuser(info.getGiftuser() + 1); service.addLiveData(liveData); break; case "COMBO_SEND"://礼物连击 data = json.getJSONObject("data"); String gift = data.getString("giftName"); if (gift == null || gift.equals("null")) { gift = data.getString("gift_name"); } danmu = data.getString("uname") + " " + data.getString("action") + " " + gift + "x" + data.getInteger("batch_combo_num"); giftData = getGiftData(data.getInteger("gift_id")); if (giftData == null) { giftData = new GiftData(); giftData.setName(data.getString("giftName")); giftData.setId(data.getInteger("giftId")); giftData.setPrice(data.getInteger("price")); giftData.setIcon(""); giftData.setDesc("这是阿B没有收录的礼物,金额可能不准(无法判别为免费礼物)"); giftData.setRights("?"); } liveData.setType(LiveData.COMBO_SEND); liveData.setUid(data.getInteger("uid")); liveData.setGiftid(giftData.getId()); liveData.setGiftindex(data.getInteger("batch_combo_num")); liveData.setGiftname(giftData.getName()); liveData.setMsg(danmu); liveData.setPrice(giftData.getPrice() * liveData.getGiftindex()); liveData.setPriceofcommission(giftData.getPrice() == 0 ? 0 : giftData.getPrice() / 2); liveData.setSubtime(new Date()); liveData.setRoomid(roomId); info.setGiftuser(info.getGiftuser() + 1); service.addLiveData(liveData); break; case "ENTRY_EFFECT"://舰长进直播间 // com.yutou.bilibili.Tools.Log.i("[舰长]" + json.getJSONObject("data").getString("uid") + " 进入到直播间"); info.setVipuserindex(info.getVipuserindex() + 1); danmu = "[舰长]" + json.getJSONObject("data").getString("uid") + " 进入到直播间"; liveData.setUid(json.getJSONObject("data").getInteger("uid")); liveData.setType(LiveData.ENTRY_EFFECT); liveData.setMsg(danmu); liveData.setRoomid(roomId); liveData.setSubtime(new Date()); service.addLiveData(liveData); break; case "LIVE_INTERACTIVE_GAME"://彩色弹幕?通过游戏弹幕 break; case "SUPER_CHAT_MESSAGE"://SC data = json.getJSONObject("data"); danmu = data.getJSONObject("user_info").getString("uname") + " " + data.getJSONObject("gift").getString("gift_name") + " " + data.getInteger("price") + "元: " + data.getString("message"); liveData.setType(LiveData.SUPER_CHAT_MESSAGE); liveData.setUid(data.getInteger("uid")); liveData.setMsg(danmu); liveData.setGiftid(0); liveData.setRoomid(roomId); liveData.setGiftname(data.getJSONObject("gift").getString("gift_name")); liveData.setGiftindex(1); liveData.setPrice(data.getInteger("price") * 1000); liveData.setPriceofcommission(liveData.getPrice() == 0 ? 0 : liveData.getPrice() / 2); liveData.setSubtime(new Date()); info.setGiftuser(info.getGiftuser() + 1); service.addLiveData(liveData); break; case "USER_TOAST_MSG": break; case "GUARD_BUY"://开通/续费 牛逼的东西 data = json.getJSONObject("data"); liveData.setType(LiveData.GUARD_BUY); liveData.setUid(data.getInteger("uid")); liveData.setMsg(data.getString("gift_name")); liveData.setGiftid(-data.getInteger("guard_level")); liveData.setGiftindex(data.getInteger("num")); liveData.setGiftname(data.getString("gift_name")); liveData.setPrice(data.getInteger("price")); liveData.setPriceofcommission(liveData.getPrice() == 0 ? 0 : liveData.getPrice() / 2); liveData.setSubtime(new Date()); liveData.setRoomid(roomId); info.setGiftuser(info.getGiftuser() + 1); service.addLiveData(liveData); break; case "SUPER_CHAT_MESSAGE_JPN": case "NOTICE_MSG": case "HOT_RANK_CHANGED"://榜单更新等无用信息 case "ONLINE_RANK_COUNT": case "ONLINE_RANK_V2": case "ONLINE_RANK_TOP3": case "ROOM_REAL_TIME_MESSAGE_UPDATE": case "WIDGET_BANNER"://鬼知道是啥 case "HOT_RANK_SETTLEMENT": case "LIVE"://开始直播,不过有在心跳包上做检测了,所以也无所谓? case "PK_BATTLE_SETTLE_V2": case "PK_BATTLE_END": case "PK_BATTLE_SETTLE": case "PK_BATTLE_PRE_NEW": case "PK_BATTLE_PRE": //com.yutou.bilibili.Tools.Log.i(msg); break; default: com.yutou.bilibili.Tools.Log.i(msg); liveData = new BilibiliLiveData(); liveData.setType(LiveData.UNKNOWN_MESSAGE); liveData.setUid(-1); liveData.setMsg(msg); liveData.setRoomid(roomId); liveData.setSubtime(new Date()); service.addLiveData(liveData); } } catch (Exception e) { e.printStackTrace(); try { JSONObject.parseObject(new String(bytes, StandardCharsets.UTF_8)); processData(new String(bytes, StandardCharsets.UTF_8), null); } catch (Exception e2) { e2.printStackTrace(); com.yutou.bilibili.Tools.Log.i(msg); com.yutou.bilibili.Tools.Log.i("---------ERROR !!-----"); LiveUtils.printHex(bytes); } } } public void clearInfo() { info = new BilibiliLiveInfo(); } public BilibiliLiveInfo getInfo() { return info; } public void setSaveDanmo(boolean saveDanmu) { upData.setSavedanmu(saveDanmu ? 1 : 0); } private static ApplicationContext applicationContext = null; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { if (Live.applicationContext == null) { Live.applicationContext = applicationContext; } } public static T getBean(Class clazz) { return applicationContext.getBean(clazz); } public BilibiliUpInfo geData() { return upData; } /** * 发送心跳包 */ public class sendHeartbeat extends TimerTask { public sendHeartbeat() { } public void run() { if (!run) { cancel(); return; } updateUpInfo(); if (upData.getOfflinelistening() != 1 && !LiveUtils.isLivePlayer(upData.getRoomid())) { stop(); } try { // com.yutou.bilibili.Tools.Log.i("-------发送心跳--------"); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); outputStream.write(LiveUtils.toLH("[object Object]".length() + 16)); outputStream.write(new byte[]{0, 16, 0, 1, 0, 0, 0, 2, 0, 0, 0, 1}); outputStream.write("[object Object]".getBytes(StandardCharsets.UTF_8)); outputStream.flush(); client.send(outputStream.toByteArray()); } catch (Exception e) { e.printStackTrace(); com.yutou.bilibili.Tools.Log.i(client.isClosed()); com.yutou.bilibili.Tools.Log.i(client.isOpen()); } } } public GiftData getGiftData(int id) { for (GiftData data : LiveUtils.LiveGiftConfig.giftDataList) { if (data.getId() == id) { return data; } } GiftData data = new GiftData(); BilibiliLiveData liveData = service.queryGiftOfId(id); if (liveData == null) { return null; } data.setName(liveData.getGiftname()); data.setPrice(liveData.getPrice()); data.setIcon(""); data.setDesc("这是阿B没有收录的礼物,金额可能不准(无法判别为免费礼物)"); data.setRights("?"); return data; } }