UNPKG

blivedmjs

Version:

B站直播弹幕库的Node.js实现 (CommonJS版本)

500 lines (450 loc) 19.2 kB
/** * 基础消息处理器 * 处理从B站直播WebSocket接收到的各类消息 * 可以通过继承此类并重写方法来自定义消息处理逻辑 */ class BaseHandler { /** * 初始化基础处理器 * 配置各类消息对应的处理函数 */ constructor() { // 命令回调字典,将B站的消息类型映射到对应的处理方法 this.CMD_CALLBACK_DICT = { 'DANMU_MSG': this._on_danmaku.bind(this), // 弹幕消息 'SEND_GIFT': this._on_gift.bind(this), // 赠送礼物 'GUARD_BUY': this._on_buy_guard.bind(this), // 购买舰长 'USER_TOAST_V2': this._on_user_toast_v2.bind(this), // 用户提示V2(舰长相关) 'SUPER_CHAT_MESSAGE': this._on_super_chat.bind(this), // 醒目留言(SC) 'SUPER_CHAT_MESSAGE_DELETE': this._on_super_chat_delete.bind(this), // SC删除 'LIKE_INFO_V3_UPDATE': this._on_like.bind(this), // 点赞信息更新 'LIKE_INFO_V3_CLICK': this._on_like_click.bind(this), // 用户点赞 'INTERACT_WORD': this._on_interact_word.bind(this), // 用户互动(进入直播间等) 'ENTRY_EFFECT': this._on_entry_effect.bind(this), // 入场特效 'COMBO_SEND': this._on_combo_send.bind(this), // 连击礼物 'HOT_RANK_CHANGED': this._on_hot_rank_changed.bind(this), // 热门榜单变化 'HOT_RANK_CHANGED_V2': this._on_hot_rank_changed_v2.bind(this), // 热门榜单变化V2 'LIVE': this._on_live.bind(this), // 直播状态变化 'LIVE_INTERACTIVE_GAME': this._on_live_interactive_game.bind(this), // 直播互动游戏 'NOTICE_MSG': this._on_notice_msg.bind(this), // 系统通知 'ONLINE_RANK_TOP3': this._on_online_rank_top3.bind(this), // 在线排名前三 'PK_BATTLE_END': this._on_pk_battle_end.bind(this), // PK结束 'PK_BATTLE_FINAL_PROCESS': this._on_pk_battle_final_process.bind(this), // PK决赛进程 'PK_BATTLE_PROCESS': this._on_pk_battle_process.bind(this), // PK进程 'PK_BATTLE_PROCESS_NEW': this._on_pk_battle_process_new.bind(this), // 新版PK进程 'PK_BATTLE_SETTLE': this._on_pk_battle_settle.bind(this), // PK结算 'PK_BATTLE_SETTLE_USER': this._on_pk_battle_settle_user.bind(this), // PK用户结算 'PK_BATTLE_SETTLE_V2': this._on_pk_battle_settle_v2.bind(this), // PK结算V2 'PREPARING': this._on_preparing.bind(this), // 直播准备中 'ROOM_REAL_TIME_MESSAGE_UPDATE': this._on_room_real_time_message_update.bind(this), // 房间实时信息更新 'SUPER_CHAT_MESSAGE_JPN': this._on_super_chat_jpn.bind(this), // 日语SC 'USER_TOAST_MSG': this._on_user_toast_msg.bind(this), // 用户提示消息 'WIDGET_BANNER': this._on_widget_banner.bind(this), // 横幅组件 'OTHER_SLICE_LOADING_RESULT': this._on_other_slice_loading_result.bind(this), // 其他切片加载结果 'DM_INTERACTION': this._on_dm_interaction.bind(this) // 弹幕互动(比如连续点赞) }; // 添加消息缓存用于去重 this._messageCache = new Map(); } /** * 客户端连接成功回调 * 当WebSocket连接成功后调用 * @param {BLiveClient} client 客户端实例 */ on_client_start(client) { // 可以重写此方法 } /** * 客户端断开连接回调 * 当WebSocket连接断开后调用 * @param {BLiveClient} client 客户端实例 */ on_client_stop(client) { // 可以重写此方法 } /** * 收到心跳包回调 * 处理服务器返回的心跳响应(包含直播间人气值) * @param {BLiveClient} client 客户端实例 * @param {HeartbeatMessage} message 心跳消息 */ _on_heartbeat(client, message) { // 心跳包不输出,可以重写此方法来处理人气值 } /** * 消息去重检查 * 防止短时间内处理重复的消息(例如多次收到同一条弹幕) * @param {string} key 消息的唯一标识 * @param {number} timeout 去重超时时间(毫秒) * @returns {boolean} 如果是重复消息返回true */ _isDuplicateMessage(key, timeout = 3000) { const now = Date.now(); const lastTime = this._messageCache.get(key); if (lastTime && now - lastTime < timeout) { return true; // 在去重时间窗口内,认为是重复消息 } this._messageCache.set(key, now); // 记录消息时间 // 清理过期的缓存项,避免内存占用过大 if (this._messageCache.size > 1000) { const expireTime = now - timeout; for (const [k, v] of this._messageCache.entries()) { if (v < expireTime) { this._messageCache.delete(k); } } } return false; // 不是重复消息 } /** * 收到弹幕回调 * 处理用户在直播间发送的文本弹幕 * @param {BLiveClient} client 客户端实例 * @param {DanmakuMessage} message 弹幕消息 */ _on_danmaku(client, message) { // 生成消息唯一标识,用于去重 const key = `danmaku_${message.timestamp}_${message.uid}_${message.msg}`; if (this._isDuplicateMessage(key)) return; // 默认行为是打印弹幕到控制台 console.log(`[${client.roomId}] ${message.uname}${message.msg}`); } /** * 收到礼物回调 * 处理用户在直播间赠送的礼物 * @param {BLiveClient} client 客户端实例 * @param {GiftMessage} message 礼物消息 */ _on_gift(client, message) { // 生成消息唯一标识,用于去重 const key = `gift_${message.uid}_${message.giftId}_${message.timestamp}`; if (this._isDuplicateMessage(key)) return; // 构建粉丝勋章信息字符串 let medal_str = ''; if (message.medal_info && message.medal_info.medal_level > 0) { medal_str = `[${message.medal_info.medal_name}${message.medal_info.medal_level}]`; } // 打印礼物信息到控制台 console.log(`[${client.roomId}] ${message.uname}${medal_str} 赠送 ${message.giftName}x${message.num} (${message.coinType === 'gold' ? '金瓜子' : '银瓜子'}x${message.totalCoin})`); } /** * 用户上舰回调 * 处理用户购买大航海服务(舰长、提督、总督) * @param {BLiveClient} client 客户端实例 * @param {GuardBuyMessage} message 上舰消息 */ _on_buy_guard(client, message) { // 打印上舰信息到控制台 // guardLevel: 3:舰长, 2:提督, 1:总督 console.log(`[${client.roomId}] ${message.username} 上舰,guard_level=${message.guardLevel}`); } /** * 醒目留言回调 * 处理用户发送的付费高亮消息(SuperChat) * @param {BLiveClient} client 客户端实例 * @param {SuperChatMessage} message SC消息 */ _on_super_chat(client, message) { // 打印SC信息到控制台 console.log(`[${client.roomId}] 醒目留言 ¥${message.price} ${message.uname}${message.message}`); } /** * 醒目留言删除回调 * 处理被删除的SC消息 * @param {BLiveClient} client 客户端实例 * @param {Object} message 删除消息 */ _on_super_chat_delete(client, message) { // 可以重写此方法处理SC删除 } /** * 点赞信息更新回调 * 处理直播间总点赞数变化 * @param {BLiveClient} client 客户端实例 * @param {LikeInfoV3UpdateMessage} message 点赞信息 */ _on_like(client, message) { // 可以重写此方法处理总点赞数变化 } /** * 用户进入直播间回调 * 处理用户的各种互动行为(进入、关注、分享等) * @param {BLiveClient} client 客户端实例 * @param {InteractWordMessage} message 互动消息 */ _on_interact_word(client, message) { // 生成消息唯一标识,用于去重 const key = `interact_${message.uid}_${message.msgType}_${message.timestamp}`; if (this._isDuplicateMessage(key)) return; // 构建粉丝勋章信息字符串 let medal_str = ''; if (message.fans_medal && message.fans_medal.medal_level > 0) { medal_str = `[${message.fans_medal.medal_name}${message.fans_medal.medal_level}]`; } // 根据消息类型确定用户行为 let action = ''; switch (message.msgType) { case 1: action = '进入房间'; break; case 2: action = '关注了主播'; break; case 3: action = '分享了直播间'; break; case 4: action = '特别关注了主播'; break; case 5: action = '与主播互粉了'; break; case 6: action = '为主播点赞了'; break; } // 打印用户互动信息到控制台 if (action) { console.log(`[${client.roomId}] ${message.uname}${medal_str} ${action}`); } } /** * 用户进入特效回调 * 处理用户进入直播间触发的特效(如舰长进场特效) * @param {BLiveClient} client 客户端实例 * @param {EntryEffectMessage} message 进入特效消息 */ _on_entry_effect(client, message) { // 默认不输出进入特效信息,可以重写此方法处理特效 } /** * 用户点赞回调 * 处理单个用户的点赞行为 * @param {BLiveClient} client 客户端实例 * @param {LikeClickMessage} message 点赞消息 */ _on_like_click(client, message) { // 生成消息唯一标识,用于去重(点赞没有时间戳,用当前时间) const key = `like_${message.uid}_${Date.now()}`; if (this._isDuplicateMessage(key)) return; // 构建粉丝勋章信息字符串 let medal_str = ''; if (message.fans_medal && message.fans_medal.medal_level > 0) { medal_str = `[${message.fans_medal.medal_name}${message.fans_medal.medal_level}]`; } // 打印点赞信息到控制台 console.log(`[${client.roomId}] ${message.uname}${medal_str} 为主播点赞了`); } /** * 用户礼物提示V2回调 * 处理新版礼物提示(主要是大航海相关) * @param {BLiveClient} client 客户端实例 * @param {UserToastV2Message} message 提示消息 */ _on_user_toast_v2(client, message) { // 打印上舰信息到控制台 console.log(`[${client.roomId}] ${message.username} 上舰,guard_level=${message.guard_level}`); } /** * 礼物连击回调 * 处理用户连续赠送同一礼物的信息 * @param {BLiveClient} client 客户端实例 * @param {ComboSendMessage} message 连击消息 */ _on_combo_send(client, message) { // 默认不输出连击礼物信息,可以重写此方法处理连击 } /** * 热门榜单变化回调 * 处理直播间在热门榜单上排名变化 * @param {BLiveClient} client 客户端实例 * @param {HotRankChangedMessage} message 榜单变化消息 */ _on_hot_rank_changed(client, message) { // 默认不输出热门榜单变化,可以重写此方法处理榜单变化 } /** * 热门榜单变化V2回调 * 处理新版热门榜单变化 * @param {BLiveClient} client 客户端实例 * @param {Object} message 榜单变化消息 */ _on_hot_rank_changed_v2(client, message) { // 默认不输出榜单变化,可以重写此方法处理新版榜单变化 } /** * 直播状态变化回调 * 处理直播开始/结束的通知 * @param {BLiveClient} client 客户端实例 * @param {LiveMessage} message 直播状态消息 */ _on_live(client, message) { // 默认不输出直播状态变化,可以重写此方法处理直播状态 } /** * 直播互动游戏回调 * 处理直播间互动游戏相关的消息 * @param {BLiveClient} client 客户端实例 * @param {Object} message 互动游戏消息 */ _on_live_interactive_game(client, message) { // 默认不输出互动游戏信息,可以重写此方法处理游戏互动 } /** * 系统通知回调 * 处理全平台或房间内的系统通知 * @param {BLiveClient} client 客户端实例 * @param {NoticeMsgMessage} message 通知消息 */ _on_notice_msg(client, message) { // 生成消息唯一标识,用于去重 const key = `notice_${message.msg_common}`; if (this._isDuplicateMessage(key)) return; // 打印系统通知到控制台 if (message.msg_common) { console.log(`[${client.roomId}] 系统通知: ${message.msg_common}`); } } /** * 在线排名前三回调 * 处理直播间高能用户前三排名 * @param {BLiveClient} client 客户端实例 * @param {Object} message 排名消息 */ _on_online_rank_top3(client, message) { // 默认不输出排名信息,可以重写此方法处理排名 } /** * PK结束回调 * 处理直播间PK对战结束 * @param {BLiveClient} client 客户端实例 * @param {Object} message PK消息 */ _on_pk_battle_end(client, message) { // 默认不输出PK结束信息,可以重写此方法处理PK结束 } /** * PK决赛进程回调 * 处理直播间PK对战决赛阶段 * @param {BLiveClient} client 客户端实例 * @param {Object} message PK消息 */ _on_pk_battle_final_process(client, message) { // 默认不输出PK决赛进程,可以重写此方法处理决赛进程 } /** * PK进程回调 * 处理直播间PK对战的状态变化 * @param {BLiveClient} client 客户端实例 * @param {PKBattleMessage} message PK消息 */ _on_pk_battle_process(client, message) { // 默认不输出PK进程,可以重写此方法处理PK进程 } /** * 新版PK进程回调 * 处理直播间新版PK对战的状态变化 * @param {BLiveClient} client 客户端实例 * @param {Object} message PK消息 */ _on_pk_battle_process_new(client, message) { // 默认不输出新版PK进程,可以重写此方法处理新版PK进程 } /** * PK结算回调 * 处理直播间PK对战结束后的结算信息 * @param {BLiveClient} client 客户端实例 * @param {PKBattleSettleMessage} message PK结算消息 */ _on_pk_battle_settle(client, message) { // 默认不输出PK结算信息,可以重写此方法处理PK结算 } /** * PK用户结算回调 * 处理直播间PK对战中单个用户的结算信息 * @param {BLiveClient} client 客户端实例 * @param {Object} message PK用户结算消息 */ _on_pk_battle_settle_user(client, message) { // 默认不输出PK用户结算,可以重写此方法处理用户结算 } /** * PK结算V2回调 * 处理直播间新版PK对战结算 * @param {BLiveClient} client 客户端实例 * @param {Object} message PK结算消息 */ _on_pk_battle_settle_v2(client, message) { // 默认不输出新版PK结算,可以重写此方法处理新版结算 } /** * 直播准备中回调 * 处理直播间进入准备状态的通知 * @param {BLiveClient} client 客户端实例 * @param {PreparingMessage} message 准备消息 */ _on_preparing(client, message) { // 默认不输出准备中信息,可以重写此方法处理准备状态 } /** * 房间实时信息更新回调 * 处理直播间粉丝数等实时数据的更新 * @param {BLiveClient} client 客户端实例 * @param {RoomRealTimeMessageUpdateMessage} message 实时信息 */ _on_room_real_time_message_update(client, message) { const key = `room_update_${message.roomid}_${message.fans}`; if (this._isDuplicateMessage(key)) return; // 打印粉丝数变化 if (message.fans) { console.log(`[${client.roomId}] 粉丝数: ${message.fans}`); } } /** * 日语SC回调 * 处理日语环境下的醒目留言 * @param {BLiveClient} client 客户端实例 * @param {Object} message 日语SC消息 */ _on_super_chat_jpn(client, message) { // 默认不特殊处理日语SC,可以重写此方法处理日语SC } /** * 用户提示消息回调 * 处理用户相关的系统提示 * @param {BLiveClient} client 客户端实例 * @param {UserToastMessage} message 提示消息 */ _on_user_toast_msg(client, message) { // 默认不输出用户提示,可以重写此方法处理用户提示 } /** * 横幅组件回调 * 处理直播间顶部横幅通知 * @param {BLiveClient} client 客户端实例 * @param {WidgetBannerMessage} message 横幅消息 */ _on_widget_banner(client, message) { // 默认不输出横幅信息,可以重写此方法处理横幅 } /** * 其他切片加载结果回调 * 处理其他切片内容的加载情况 * @param {BLiveClient} client 客户端实例 * @param {Object} message 加载结果消息 */ _on_other_slice_loading_result(client, message) { // 默认不输出其他切片加载结果,可以重写此方法处理加载结果 } /** * 弹幕互动回调 * 处理弹幕互动相关的消息(比如连续点赞) * @param {BLiveClient} client 客户端实例 * @param {Object} message 弹幕互动消息 */ _on_dm_interaction(client, message) { // 默认不输出弹幕互动信息,可以重写此方法处理弹幕互动 // 一般是处理连续点赞等互动特效 } } module.exports = BaseHandler;