Files
DanMuRead/Demos/NodeJS/main.js
T

312 lines
11 KiB
JavaScript
Raw Normal View History

2025-04-30 22:53:19 +08:00
/**
* 抖音直播间消息处理脚本
* 消息类型说明:
* 1[普通弹幕]|2[点赞消息]|3[进入直播间]|4[关注消息]|5[礼物消息]|6[统计消息]|7[粉丝团消息]|8[直播间分享]|9[下播]
*
* 全局统计变量:
* Live_Total - 本场点赞数
* Live_user - 本场总观看人数
* Live_man - 本场男生人数
* Live_woman - 本场女生人数
*/
const WebSocket = require('ws');
const chalk = require('chalk');
const { PackMsgType, Gender, LiveStats } = require('./entities');
const { MessageParser } = require('./parsers');
// 颜色定义
const COLOR_MAP = {
[PackMsgType.弹幕消息]: chalk.cyan, // 青色
[PackMsgType.点赞消息]: chalk.yellow, // 黄色
[PackMsgType.进直播间]: chalk.green, // 绿色
[PackMsgType.关注消息]: chalk.magenta, // 洋红色
[PackMsgType.礼物消息]: chalk.red, // 红色
[PackMsgType.直播间统计]: chalk.blue, // 蓝色
[PackMsgType.粉丝团消息]: chalk.blue.bright, // 浅蓝色
[PackMsgType.直播间分享]: chalk.magenta.bright, // 浅洋红色
[PackMsgType.下播]: chalk.red, // 红色
};
// 初始化统计对象
const liveStats = new LiveStats();
/**
* 返回当前时间戳,格式:HH:MM:SS
* @returns {string} 格式化的时间戳
*/
function getTimestamp() {
const now = new Date();
return now.toTimeString().split(' ')[0];
}
/**
* 打印带颜色的消息
* @param {number} msgType - 消息类型
* @param {string} content - 消息内容
* @param {string} [author="未知"] - 消息来源作者
*/
function printColoredMessage(msgType, content, author = "未知") {
const colorFunc = COLOR_MAP[msgType] || chalk.white;
const timestamp = getTimestamp();
const authorStr = `[${author}]`; // 使用作者信息
console.log(colorFunc(`${timestamp} ${authorStr} ${content}`));
}
/**
* 统一获取消息所属的主播/房间信息
* @param {Object} msg - 解析后的消息对象
* @returns {string} 主播昵称或房间ID
*/
function getMsgOwner(msg) {
if (msg && msg.Owner && msg.Owner.Nickname) {
return msg.Owner.Nickname;
} else if (msg && msg.WebRoomId) {
return msg.WebRoomId;
} else if (msg && msg.RoomId) {
return msg.RoomId;
}
return "未知";
}
/**
* 处理用户发送的弹幕消息
* @param {Object} dataDict - 弹幕消息数据
*/
function handleDanmaku(dataDict) {
const msg = MessageParser.parseDanmaku(dataDict);
const genderStr = msg.User.genderToString();
const content = `[弹幕消息] [${genderStr}] ${msg.User.Nickname}: ${msg.Content}`;
const author = getMsgOwner(msg);
printColoredMessage(PackMsgType.弹幕消息, content, author);
}
/**
* 处理用户点赞消息并更新全局统计
* @param {Object} dataDict - 点赞消息数据
*/
function handleDianzanku(dataDict) {
const likeMsg = MessageParser.parseLike(dataDict);
liveStats.total_likes = likeMsg.Total;
const genderStr = likeMsg.User.genderToString();
const content = `[点赞消息] [${genderStr}] ${likeMsg.User.Nickname} 为主播点了${likeMsg.Count}个赞, 总点赞${likeMsg.Total}`;
const author = getMsgOwner(likeMsg);
printColoredMessage(PackMsgType.点赞消息, content, author);
}
/**
* 处理用户进入直播间的消息,并根据性别更新统计
* @param {Object} dataDict - 进入直播间消息数据
*/
function handleUserentry(dataDict) {
const memberMsg = MessageParser.parseMember(dataDict);
// 根据性别更新全局计数
if (memberMsg.User.Gender === Gender.男) {
liveStats.male_users += 1;
} else if (memberMsg.User.Gender === Gender.女) {
liveStats.female_users += 1;
}
const genderStr = memberMsg.User.genderToString();
const content = `[进直播间] [${genderStr}] ${memberMsg.User.Nickname} $来了 直播间人数:${memberMsg.CurrentCount}`;
const author = getMsgOwner(memberMsg);
printColoredMessage(PackMsgType.进直播间, content, author);
}
/**
* 处理用户关注主播的消息
* @param {Object} dataDict - 关注消息数据
*/
function handleFollow(dataDict) {
try {
const userNickname = dataDict.User?.Nickname || "匿名用户";
const gender = dataDict.User?.Gender || 0;
const genderStr = Gender.toString(gender);
const content = `[关注消息] [${genderStr}] ${userNickname} 关注了主播`;
const author = getMsgOwner(dataDict);
printColoredMessage(PackMsgType.关注消息, content, author);
} catch (e) {
console.log(`处理关注消息出错: ${e.message}`);
}
}
/**
* 处理用户赠送礼物的消息
* @param {Object} dataDict - 礼物消息数据
*/
function handleGift(dataDict) {
try {
const userNickname = dataDict.User?.Nickname || "匿名用户";
const gender = dataDict.User?.Gender || 0;
const genderStr = Gender.toString(gender);
const giftName = dataDict.GiftName || "未知礼物";
const giftCount = dataDict.GiftCount || 1;
const giftValue = dataDict.GiftValue || 0;
const content = `[礼物消息] [${genderStr}] ${userNickname} 送出了 ${giftCount}个${giftName}, 价值${giftValue}抖币`;
const author = getMsgOwner(dataDict);
printColoredMessage(PackMsgType.礼物消息, content, author);
} catch (e) {
console.log(`处理礼物消息出错: ${e.message}`);
}
}
/**
* 处理直播间统计信息
* @param {Object} dataDict - 统计消息数据
*/
function handleStatistics(dataDict) {
const statsMsg = MessageParser.parseStatistics(dataDict);
liveStats.total_users = parseInt(statsMsg.TotalUserCount);
const content = `[直播间统计] 当前直播间人数 ${statsMsg.OnlineUserCountStr}, 累计直播间人数 ${statsMsg.TotalUserCountStr}万`;
const author = getMsgOwner(statsMsg);
printColoredMessage(PackMsgType.直播间统计, content, author);
}
/**
* 处理粉丝团相关消息
* @param {Object} dataDict - 粉丝团消息数据
*/
function handleFansclub(dataDict) {
try {
const userNickname = dataDict.User?.Nickname || "匿名用户";
const gender = dataDict.User?.Gender || 0;
const genderStr = Gender.toString(gender);
const clubName = dataDict.FansClubName || "";
const content = `[粉丝团消息] [${genderStr}] ${userNickname} 加入了${clubName}粉丝团`;
const author = getMsgOwner(dataDict);
printColoredMessage(PackMsgType.粉丝团消息, content, author);
} catch (e) {
console.log(`处理粉丝团消息出错: ${e.message}`);
}
}
/**
* 处理用户分享直播间的消息
* @param {Object} dataDict - 分享消息数据
*/
function handleShare(dataDict) {
try {
const userNickname = dataDict.User?.Nickname || "匿名用户";
const gender = dataDict.User?.Gender || 0;
const genderStr = Gender.toString(gender);
const content = `[直播间分享] [${genderStr}] ${userNickname} 分享了直播间`;
const author = getMsgOwner(dataDict);
printColoredMessage(PackMsgType.直播间分享, content, author);
} catch (e) {
console.log(`处理分享消息出错: ${e.message}`);
}
}
/**
* 处理直播结束事件,显示并重置统计数据
*/
function handleLiveExit() {
const content = `[下播] 直播结束:累计观看 ${liveStats.total_users} 人,累计点赞 ${liveStats.total_likes},` +
`男生 ${liveStats.male_users} 女生 ${liveStats.female_users}`;
printColoredMessage(PackMsgType.下播, content, "系统消息");
liveStats.reset();
console.log(`直播统计数据已清空~`);
}
/**
* 处理从WebSocket接收到的单条消息
* @param {string} message - WebSocket接收到的消息
*/
function processMessage(message) {
try {
const data = JSON.parse(message);
if (!("Type" in data)) {
console.log("⚠️ 收到无效消息:缺少Type字段");
return;
}
const msgType = data.Type;
// 检查是否需要处理Data字段
if (msgType === PackMsgType.下播) {
handleLiveExit();
return;
}
if (!("Data" in data)) {
console.log(`⚠️ 消息类型 ${Object.keys(PackMsgType).find(key => PackMsgType[key] === msgType)} 缺少Data字段`);
return;
}
try {
const dataDict = JSON.parse(data.Data);
// 消息处理映射表
const handlers = {
[PackMsgType.弹幕消息]: handleDanmaku,
[PackMsgType.点赞消息]: handleDianzanku,
[PackMsgType.进直播间]: handleUserentry,
[PackMsgType.关注消息]: handleFollow,
[PackMsgType.礼物消息]: handleGift,
[PackMsgType.直播间统计]: handleStatistics,
[PackMsgType.粉丝团消息]: handleFansclub,
[PackMsgType.直播间分享]: handleShare,
};
// 分发消息到相应处理器
if (handlers[msgType]) {
handlers[msgType](dataDict);
}
} catch (e) {
if (e instanceof SyntaxError) {
console.log(`⚠️ 无法解析Data字段内容:${data.Data.substring(0, 100)}...`);
} else {
throw e;
}
}
} catch (e) {
if (e instanceof SyntaxError) {
console.log(`⚠️ 无效的JSON格式: ${message.substring(0, 100)}...`);
} else {
console.log(`❌ 消息处理错误: ${e.message}`);
}
}
}
/**
* 连接到WebSocket服务器并处理接收到的消息
*/
function connectAndPrint() {
const uri = "ws://127.0.0.1:8888";
let reconnectInterval = 5000; // 重连间隔,单位毫秒
console.log(`正在连接到服务器 ${uri}...`);
function connect() {
const ws = new WebSocket(uri);
ws.on('open', function open() {
console.log(`✅ 已成功连接到服务器 ${uri}`);
});
ws.on('message', function incoming(message) {
processMessage(message.toString());
});
ws.on('close', function close() {
console.log(`⚠️ 与服务器的连接已关闭`);
console.log(`正在尝试重新连接...`);
setTimeout(connect, reconnectInterval);
});
ws.on('error', function error(err) {
console.log(`❌ 发生错误: ${err.message}`);
// 错误处理由 close 事件处理重连
});
}
connect();
}
// 程序入口
console.log("启动抖音直播消息监听...");
connectAndPrint();