2021-04-08 18:33:34 +08:00

604 lines
24 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package com.yutou.bilibili.BiliBili;
import com.alibaba.fastjson.JSONArray;
import com.yutou.bilibili.BiliBili.Datas.DanmuData;
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.QQBot.QQBotManager;
import com.yutou.bilibili.Tools.AppTools;
import com.yutou.bilibili.Tools.Log;
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 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<Live> lives = new ArrayList<>();
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;
public Timer checkLiveTimer;
@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;
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);
checkLiveTimer = new Timer();
checkLiveTimer.schedule(new TimerTask() {
@Override
public void run() {
checkLive();
}
}, 0, 1000);
}
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 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<String, String> header = new HashMap<String, String>();
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) {
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) {
System.err.println(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);
}
@Override
public void onError(Exception e) {
Log.e(e);
run = false;
client.close();
}
};
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(this);
com.yutou.bilibili.Tools.Log.i("退出" + roomId + "直播间");
if (checkLiveTimer != null) {
checkLiveTimer.cancel();
}
}
/**
* 连接房间
*
* @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<String> 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) {
checkLive();
int popular = LiveUtils.bytesToInt2(bytes, 0);
info.setPopular(popular);
}
}
} catch (Exception e) {
Log.e(e);
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()));
}
}
public void checkLive() {
boolean isLive = LiveUtils.isLivePlayer(roomId);
upData.setLive(isLive ? 1 : 0);
if (isLive && upData.getSavelive() == 1) {
SaveLive.getInstance().addLive(this);
}
if (!isLive || upData.getSavelive() == 0) {
if (SaveLive.getInstance().checkLive(roomId)) {
SaveLive.getInstance().stop(roomId);
}
}
}
/**
* 处理数据
*
* @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"://普通弹幕
JSONArray infoData = json.getJSONArray("info");
danmu = json.getJSONArray("info").getString(1);
DanmuData danmuData = new DanmuData();
danmuData.setModel(infoData.getJSONArray(0).getInteger(1));
danmuData.setFontSize(infoData.getJSONArray(0).getInteger(2));
danmuData.setFontColor(infoData.getJSONArray(0).getInteger(3));
danmuData.setTime(infoData.getJSONArray(0).getLong(4));
danmuData.setUCode(infoData.getJSONArray(0).getString(7));
danmuData.setDanmu(danmu);
danmuData.setUid(infoData.getJSONArray(2).getInteger(0));
danmuData.setUname(infoData.getJSONArray(2).getString(1));
if (upData != null && upData.getSavedanmu() == 1) {
Log.i(danmuData.toString());
if (danmuManager != null)
danmuManager.addDanmu(danmuData);
}
// 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"://开始直播,不过有在心跳包上做检测了,所以也无所谓?
JSONObject liveInfo = LiveUtils.getLiveInfo(roomId);
StringBuilder builder = new StringBuilder();
builder.append(upData.getName()).append("开播了!").append("\n");
builder.append(liveInfo.getJSONObject("data").getJSONObject("room_info").getString("title"));
Tools.download(liveInfo.getJSONObject("data").getJSONObject("room_info").getString("keyframe"), new DownloadInterface() {
@Override
public void onDownload(File file) {
super.onDownload(file);
QQBotManager.getInstance().sendMessage(file, builder.toString());
}
});
break;
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) {
Log.e(e);
try {
JSONObject.parseObject(new String(bytes, StandardCharsets.UTF_8));
processData(new String(bytes, StandardCharsets.UTF_8), null);
} catch (Exception e2) {
Log.e(e2);
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> T getBean(Class<T> 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) {
Log.e(e);
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;
}
}