@bililive-tools/bilibili-recorder
Version:
bililive-tools bilibili recorder implemention
185 lines (184 loc) • 6.78 kB
JavaScript
import { EventEmitter } from "node:events";
import { startListen } from "blive-message-listener";
import { getBuvidConf } from "./bilibili_api.js";
// 全局缓存,一天过期时间 (24 * 60 * 60 * 1000 ms)
const CACHE_DURATION = 24 * 60 * 60 * 1000;
let buvidCache = null;
// 获取带缓存的 buvid 配置
async function getCachedBuvidConf() {
const now = Date.now();
// 检查缓存是否有效
if (buvidCache && now - buvidCache.timestamp < CACHE_DURATION) {
return buvidCache.data;
}
// 缓存失效或不存在,重新获取(带重试)
const info = await getBuvidConfWithRetry();
buvidCache = {
data: info,
timestamp: now,
};
return info;
}
// 带重试功能的 getBuvidConf
async function getBuvidConfWithRetry(maxRetries = 3, retryDelay = 1000) {
let lastError;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const result = await getBuvidConf();
return result;
}
catch (error) {
lastError = error;
// 如果是最后一次尝试,直接抛出错误
if (attempt === maxRetries) {
throw error;
}
// 等待指定时间后重试,使用指数退避策略
const delay = retryDelay * Math.pow(2, attempt - 1);
await new Promise((resolve) => setTimeout(resolve, delay));
}
}
// 这里不应该到达,但为了类型安全
throw lastError;
}
class DanmaClient extends EventEmitter {
client = null;
roomId;
auth;
uid;
retryCount = 10;
useServerTimestamp;
constructor(roomId, { auth, uid, useServerTimestamp, }) {
super();
this.roomId = roomId;
this.auth = auth;
this.uid = uid;
this.useServerTimestamp = useServerTimestamp ?? true;
}
async start() {
const info = await getCachedBuvidConf();
const buvid3 = info.data.b_3;
const handler = {
onIncomeDanmu: (msg) => {
let content = msg.body.content;
content = content.replace(/(^\s*)|(\s*$)/g, "").replace(/[\r\n]/g, "");
if (content === "")
return;
const comment = {
type: "comment",
timestamp: this.useServerTimestamp ? msg.body.timestamp : Date.now(),
text: content,
color: msg.body.content_color,
mode: msg.body.type,
sender: {
uid: String(msg.body.user.uid),
name: msg.body.user.uname,
avatar: msg.body.user.face,
// extra: {
// badgeName: msg.body.user?.badge?.name,
// badgeLevel: msg.body.user?.badge?.level,
// },
},
};
this.emit("Message", comment);
},
onIncomeSuperChat: (msg) => {
const content = msg.body.content.replaceAll(/[\r\n]/g, "");
const comment = {
type: "super_chat",
timestamp: this.useServerTimestamp ? msg.raw.send_time : Date.now(),
text: content,
price: msg.body.price,
sender: {
uid: String(msg.body.user.uid),
name: msg.body.user.uname,
avatar: msg.body.user.face,
// extra: {
// badgeName: msg.body.user?.badge?.name,
// badgeLevel: msg.body.user?.badge?.level,
// },
},
};
this.emit("Message", comment);
},
onGuardBuy: (msg) => {
const gift = {
type: "guard",
timestamp: this.useServerTimestamp ? msg.timestamp : Date.now(),
name: msg.body.gift_name,
price: msg.body.price / 1000,
count: 1,
level: msg.body.guard_level,
sender: {
uid: String(msg.body.user.uid),
name: msg.body.user.uname,
avatar: msg.body.user.face,
// extra: {
// badgeName: msg.body.user?.badge?.name,
// badgeLevel: msg.body.user?.badge?.level,
// },
},
};
this.emit("Message", gift);
},
onGift: (msg) => {
const gift = {
type: "give_gift",
timestamp: this.useServerTimestamp ? msg?.raw?.data?.timestamp * 1000 : Date.now(),
name: msg.body.gift_name,
count: msg.body.amount,
price: msg.body.coin_type === "silver" ? 0 : msg.body.price / 1000,
sender: {
uid: String(msg.body.user.uid),
name: msg.body.user.uname,
avatar: msg.body.user.face,
// extra: {
// badgeName: msg.body.user?.badge?.name,
// badgeLevel: msg.body.user?.badge?.level,
// },
},
extra: {
hits: msg.body.combo?.combo_num,
},
};
this.emit("Message", gift);
},
onRoomInfoChange: (msg) => {
this.emit("RoomInfoChange", msg);
},
};
let lastAuth = "";
if (this.auth?.includes("buvid3")) {
lastAuth = this.auth;
}
else {
if (this.auth) {
lastAuth = `${this.auth}; buvid3=${buvid3}`;
}
else {
lastAuth = `buvid3=${buvid3}`;
}
}
this.client = startListen(this.roomId, handler, {
ws: {
headers: {
Cookie: lastAuth,
},
uid: this.uid ?? 0,
},
});
this.client.live.on("error", (err) => {
this.retryCount -= 1;
if (this.retryCount > 0) {
setTimeout(() => {
this.client && this.client.reconnect();
}, 2000);
}
this.emit("error", err);
});
}
stop() {
this.client?.close();
}
}
export default DanmaClient;