package com.yutou.bilibili.BiliBili; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import com.yutou.bilibili.BiliBili.Datas.DanmuData; import com.yutou.bilibili.BiliBili.Datas.GiftData; import com.yutou.bilibili.BiliBili.Datas.LiveData; import com.yutou.bilibili.BiliBili.Services.IBiliBiliLiveService; import com.yutou.bilibili.BiliBili.Tools.SaveLive; import com.yutou.bilibili.QQBot.QQBotManager; import com.yutou.bilibili.Tools.AppTools; import com.yutou.bilibili.Tools.Log; import com.yutou.bilibili.Tools.RedisTools; import com.yutou.bilibili.Tools.Tools; import com.yutou.bilibili.interfaces.DownloadInterface; 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 lombok.Data; import org.java_websocket.client.WebSocketClient; import org.java_websocket.handshake.ServerHandshake; import org.jetbrains.annotations.NotNull; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import javax.annotation.PostConstruct; 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 Map lives = new HashMap<>(); private int roomId; private boolean isLogin; private boolean run; private int userId; private WebSocketClient client; private BilibiliLiveInfo info; private BilibiliUpInfo upData; private Timer heartBeattimer; public BiliBiliLiveDatabasesManager danmuManager = null; private LiveInfo liveInfo; public LiveInfo getLiveInfo() { return liveInfo; } public void setLiveInfo(LiveInfo liveInfo) { this.liveInfo = liveInfo; } @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) { if (Live.lives.containsKey(roomId)) { Log.i("已经在统计:" + roomId); return; } this.roomId = roomId; this.isLogin = isLogin; upData = new BilibiliUpInfo(); upData.setRoomid(roomId); info.setRoomid(roomId); info.setGiftuser(0); info.setVipuserindex(0); info.setUserindex(0); Live.lives.put(roomId, this); updateUpInfo(); com.yutou.bilibili.Tools.Log.i("roomId = " + roomId + ", isLogin = " + isLogin); try { start(); } catch (Exception e) { e.printStackTrace(); } } private void updateUpInfo() { try { upData = service.queryUp(upData); //upData.setLive(1); if (upData.getSavedanmu() == 1) { danmuManager = new BiliBiliLiveDatabasesManager(); danmuManager.init("[" + AppTools.getToDayTime() + "]" + upData.getRoomid()); } else { if (danmuManager != null) { danmuManager.close(); danmuManager = null; } } } catch (Exception e) { Log.e(e); } } private Date startTime; /** * 开始监听 * * @throws Exception 发生异常 */ private void start() throws Exception { run = true; if (LiveUtils.isLivePlayer(roomId)) { upData.setLive(-1); } String url = LiveUtils.getLiveUrl(roomId); if (url == null) { stop(); return; } startTime = new Date(); 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"); if (client != null && !client.isClosed()) { stop(); return; } 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) { Log.e(e); } } @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) { Log.i(roomId + " 断开连接,重连..."); try { init = true; heartBeattimer.cancel(); client.close(); start(); } catch (Exception e) { Log.e(e); } 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); run = false; client.close(); stop(); } @Override public void onError(Exception e) { Log.e(e); run = false; client.close(); stop(); } }; client.connect(); } public void stop() { run = false; client.close(); if (danmuManager != null) { danmuManager.close(); } if (SaveLive.getInstance().checkLive(roomId)) { SaveLive.getInstance().stop(roomId); } Live.lives.remove(roomId); 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 != null && 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 != null && 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"); if (http == null) { stop(); return; } 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")); 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()); checkLive(); } /** * 解压缩 * * @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); String tmp = ""; for (int i=0;i T getBean(Class clazz) { return applicationContext.getBean(clazz); } public BilibiliUpInfo geData() { return upData; } /** * 发送心跳包 */ public class sendHeartbeat extends TimerTask { public sendHeartbeat() { } @Override public void run() { if (!run) { cancel(); return; } updateUpInfo(); if (upData.getOfflinelistening() != 1) { if (!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) { Log.e(e); com.yutou.bilibili.Tools.Log.i(client.isClosed()); com.yutou.bilibili.Tools.Log.i(client.isOpen()); } } } public GiftData getGiftData(int id) { JSONObject item = JSONObject.parseObject(RedisTools.get("bili_gift_" + id, 2)); GiftData data = new GiftData(); data.setPrice(item.getInteger("price")); data.setName(item.getString("name")); data.setId(item.getInteger("id")); data.setRights(item.getString("rights")); data.setIcon(item.getString("img_basic")); data.setDesc(item.getString("desc")); return data; } @Data public static class LiveInfo { private String title; private String keyframe; private int roomId; private int shortId; private JSONObject info; } }