b站协议升级
lhr0909 opened this issue · comments
前几天还能用,今天发现protover升级到3,原来的协议其实还能连接和heartbeat,但是出了一个新的ver 3的封包,用的是BrotliDecode。我本来想直接用Comen的包来做弹幕解析的,但是发现了这个问题,所以搬了一下b站的最新代码,并且使用brotliDecode算法可以正常解包。
附上我的部分代码:
const WebSocket = require("ws");
const _ = require("lodash");
const { BrotliDecode } = require("./brotli");
const { WS_CONSTANTS, WS_BINARY_HEADER_LIST } = require("./constants");
const { getRoomIdAndToken } = require("./server");
function toUint8Array(bufferObject) {
var arrayBuffer = new ArrayBuffer(bufferObject.length);
var typedArray = new Uint8Array(arrayBuffer);
for (var i = 0; i < bufferObject.length; ++i) {
typedArray[i] = bufferObject[i];
}
return typedArray;
}
function connectDanmu(roomId, token) {
const ws = new WebSocket("wss://tx-gz-live-comet-02.chat.bilibili.com/sub", {
origin: "https://live.bilibili.com",
});
ws.on("message", (payload) => {
console.log(payload);
const data = decodeData(payload);
console.log(data);
console.log(data.body);
});
ws.on("open", () => {
ws.send(
packageObject(7, {
uid: 0,
roomid: Number(roomId),
protover: 3,
platform: "web",
type: 2,
key: token,
})
);
// heartbeat packets
console.log("heartbeat");
ws.send(packageHeartbeat());
const heartbeat = setInterval(() => {
console.log("heartbeat");
ws.send(packageHeartbeat());
}, 30 * 1000);
});
ws.on("close", (code, reason) => {
console.log("close", code, reason);
});
ws.on("error", (err) => {
console.error(err);
});
}
function packageHeartbeat() {
const body = new TextEncoder().encode({});
return packageBinary(2, body);
}
function packageBinary(type, body) {
// console.log("packageBinary", type, body);
const tmp = new Uint8Array(16 + body.byteLength);
const headDataView = new DataView(tmp.buffer);
headDataView.setInt32(0, tmp.byteLength);
headDataView.setInt16(4, 16);
headDataView.setInt16(6, 1);
headDataView.setInt32(8, type); // verify
headDataView.setInt32(12, 1);
tmp.set(body, 16);
// console.log(tmp);
return tmp;
}
function packageObject(type, bufferObj) {
// console.log("packageObject", type, bufferObj);
return packageBinary(
type,
new TextEncoder().encode(JSON.stringify(bufferObj))
);
}
function decodeData(buffer) {
const arr = toUint8Array(buffer);
const dataView = new DataView(arr.buffer);
const result = {
body: [],
};
result.packetLen = dataView.getInt32(WS_CONSTANTS.WS_PACKAGE_OFFSET);
WS_BINARY_HEADER_LIST.forEach((header) => {
if (header.bytes === 4) {
result[header.key] = dataView.getInt32(header.offset);
}
if (header.bytes === 2) {
result[header.key] = dataView.getInt16(header.offset);
}
});
console.log(result);
if (
!result.op ||
(WS_CONSTANTS.WS_OP_MESSAGE !== result.op &&
result.op !== WS_CONSTANTS.WS_OP_CONNECT_SUCCESS)
) {
result.op &&
WS_CONSTANTS.WS_OP_HEARTBEAT_REPLY === result.op &&
(result.body = {
count: dataView.getInt32(WS_CONSTANTS.WS_PACKAGE_HEADER_TOTAL_LENGTH),
});
} else {
console.log("parsing non heartbeats");
for (
let cursor = WS_CONSTANTS.WS_PACKAGE_OFFSET,
end = result.packetLen,
start = "",
payload = "";
cursor < buffer.byteLength;
cursor += end
) {
(end = dataView.getInt32(cursor)),
(start = dataView.getInt16(cursor + WS_CONSTANTS.WS_HEADER_OFFSET));
try {
if (result.ver === WS_CONSTANTS.WS_BODY_PROTOCOL_VERSION_NORMAL) {
console.log(cursor, start, end);
var normalDecoded = new TextDecoder().decode(
buffer.slice(cursor + start, cursor + end)
);
payload =
0 !== normalDecoded.length ? JSON.parse(normalDecoded) : null;
} else if (
result.ver === WS_CONSTANTS.WS_BODY_PROTOCOL_VERSION_BROTLI
) {
var slice = buffer.slice(cursor + start, cursor + end),
brotliDecoded = BrotliDecode(toUint8Array(slice));
result.body = decodeData(Buffer.from(brotliDecoded)).body;
}
payload && result.body.push(payload);
} catch (err) {
console.error(
"decode body error:",
new Uint8Array(buffer),
result,
err
);
}
}
}
return result;
}
getRoomIdAndToken(process.env.ROOM_ID || "1367262").then(
({ roomId, token }) => {
console.log(roomId, token);
connectDanmu(roomId, token);
}
);
我刚实现并测试了新协议,没有什么问题。但旧协议同样也没有什么问题(当客户端第一个package发送protover等于2时,接收的包也应当只有2,目前没有发现特例。如果是混杂了不同协议的包,目前的策略也仅为忽略而不会造成不可恢复的异常)。暂时决定维持现状。
@3Shain 好的 如果旧协议没影响就好 因为我最近测试的时候发现线上的版本接收不了弹幕了 浏览器看封包的话的确发现有压缩过的封包 所以我开这个issue 另外也感谢作者及时添加新协议解析(最近在做b站视频 估计会提到您的repo 哈哈哈)
Cheers!