完善 python 和 node Demo
This commit is contained in:
@@ -0,0 +1,256 @@
|
||||
/**
|
||||
* 消息类型枚举
|
||||
*/
|
||||
const PackMsgType = {
|
||||
无: 0,
|
||||
弹幕消息: 1,
|
||||
点赞消息: 2,
|
||||
进直播间: 3,
|
||||
关注消息: 4,
|
||||
礼物消息: 5,
|
||||
直播间统计: 6,
|
||||
粉丝团消息: 7,
|
||||
直播间分享: 8,
|
||||
下播: 9
|
||||
};
|
||||
|
||||
/**
|
||||
* 性别枚举
|
||||
*/
|
||||
const Gender = {
|
||||
未知: 0,
|
||||
男: 1,
|
||||
女: 2,
|
||||
|
||||
/**
|
||||
* 将性别枚举转换为中文字符串
|
||||
* @param {number} gender - 性别枚举值
|
||||
* @returns {string} 性别的中文表示
|
||||
*/
|
||||
toString(gender) {
|
||||
switch(gender) {
|
||||
case this.男:
|
||||
return "男";
|
||||
case this.女:
|
||||
return "女";
|
||||
default:
|
||||
return "妖";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 粉丝团消息类型
|
||||
*/
|
||||
const FansclubType = {
|
||||
无: 0,
|
||||
粉丝团升级: 1,
|
||||
加入粉丝团: 2
|
||||
};
|
||||
|
||||
/**
|
||||
* 直播间分享目标
|
||||
*/
|
||||
const ShareType = {
|
||||
未知: 0,
|
||||
微信: 1,
|
||||
朋友圈: 2,
|
||||
微博: 3,
|
||||
QQ空间: 4,
|
||||
QQ: 5,
|
||||
抖音好友: 112
|
||||
};
|
||||
|
||||
/**
|
||||
* 观众的进入方式
|
||||
*/
|
||||
const EnterType = {
|
||||
正常进入: 0,
|
||||
通过分享进入: 6
|
||||
};
|
||||
|
||||
/**
|
||||
* 粉丝团信息类
|
||||
*/
|
||||
class FansClubInfo {
|
||||
constructor(data = {}) {
|
||||
this.ClubName = data.ClubName || "";
|
||||
this.Level = data.Level || 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户信息类
|
||||
*/
|
||||
class MsgUser {
|
||||
constructor(data = {}) {
|
||||
this.Id = data.Id || 0;
|
||||
this.IsAdmin = data.IsAdmin || false;
|
||||
this.IsAnchor = data.IsAnchor || false;
|
||||
this.ShortId = data.ShortId || 0;
|
||||
this.DisplayId = data.DisplayId || "";
|
||||
this.Nickname = data.Nickname || "匿名用户";
|
||||
this.Level = data.Level || 0;
|
||||
this.PayLevel = data.PayLevel || 0;
|
||||
this.Gender = data.Gender || Gender.未知;
|
||||
this.HeadImgUrl = data.HeadImgUrl || "";
|
||||
this.SecUid = data.SecUid || "";
|
||||
this.FansClub = null;
|
||||
this.FollowerCount = data.FollowerCount || 0;
|
||||
this.FollowStatus = data.FollowStatus || 0;
|
||||
this.FollowingCount = data.FollowingCount || 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回性别的中文表示
|
||||
* @returns {string} 性别的中文表示
|
||||
*/
|
||||
genderToString() {
|
||||
return Gender.toString(this.Gender);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 直播间主播信息类
|
||||
*/
|
||||
class RoomAnchorInfo {
|
||||
constructor(data = {}) {
|
||||
this.UserId = data.UserId || "";
|
||||
this.SecUid = data.SecUid || "";
|
||||
this.Nickname = data.Nickname || "";
|
||||
this.HeadUrl = data.HeadUrl || "";
|
||||
this.FollowStatus = data.FollowStatus || 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 基础消息类
|
||||
*/
|
||||
class Msg {
|
||||
constructor(data = {}) {
|
||||
this.MsgId = data.MsgId || 0;
|
||||
this.User = new MsgUser();
|
||||
this.Owner = null;
|
||||
this.Content = data.Content || "";
|
||||
this.RoomId = data.RoomId || "";
|
||||
this.WebRoomId = data.WebRoomId || "";
|
||||
this.Appid = data.Appid || "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 点赞消息类
|
||||
*/
|
||||
class LikeMsg extends Msg {
|
||||
constructor(data = {}) {
|
||||
super(data);
|
||||
this.Count = data.Count || 0;
|
||||
this.Total = data.Total || 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 礼物消息类
|
||||
*/
|
||||
class GiftMsg extends Msg {
|
||||
constructor(data = {}) {
|
||||
super(data);
|
||||
this.GiftId = data.GiftId || 0;
|
||||
this.GiftName = data.GiftName || "";
|
||||
this.GroupId = data.GroupId || 0;
|
||||
this.GiftCount = data.GiftCount || 0;
|
||||
this.RepeatCount = data.RepeatCount || 0;
|
||||
this.DiamondCount = data.DiamondCount || 0; // 抖币价格
|
||||
this.Combo = data.Combo || false; // 该礼物是否可连击
|
||||
this.ImgUrl = data.ImgUrl || ""; // 礼物图片地址
|
||||
this.ToUser = null; // 送礼目标(连麦直播间有用)
|
||||
this.GiftValue = data.GiftValue || 0; // 在原Python代码中使用的礼物价值字段
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 进入直播间消息类
|
||||
*/
|
||||
class MemberMessage extends Msg {
|
||||
constructor(data = {}) {
|
||||
super(data);
|
||||
this.CurrentCount = data.CurrentCount || 0;
|
||||
this.EnterTipType = data.EnterTipType || 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 直播间统计消息类
|
||||
*/
|
||||
class UserSeqMsg extends Msg {
|
||||
constructor(data = {}) {
|
||||
super(data);
|
||||
this.OnlineUserCount = data.OnlineUserCount || 0;
|
||||
this.TotalUserCount = data.TotalUserCount || 0;
|
||||
this.TotalUserCountStr = data.TotalUserCountStr || "0";
|
||||
this.OnlineUserCountStr = data.OnlineUserCountStr || "0";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 粉丝团消息类
|
||||
*/
|
||||
class FansclubMsg extends Msg {
|
||||
constructor(data = {}) {
|
||||
super(data);
|
||||
this.Type = data.Type || 0; // 粉丝团消息类型,升级1,加入2
|
||||
this.Level = data.Level || 0; // 粉丝团等级
|
||||
this.FansClubName = data.FansClubName || ""; // 粉丝团名称
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 直播间分享类
|
||||
*/
|
||||
class ShareMessage extends Msg {
|
||||
constructor(data = {}) {
|
||||
super(data);
|
||||
this.ShareType = data.ShareType || ShareType.未知;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 直播间统计数据类
|
||||
*/
|
||||
class LiveStats {
|
||||
constructor() {
|
||||
this.total_likes = 0;
|
||||
this.total_users = 0;
|
||||
this.male_users = 0;
|
||||
this.female_users = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置统计数据
|
||||
*/
|
||||
reset() {
|
||||
this.total_likes = 0;
|
||||
this.total_users = 0;
|
||||
this.male_users = 0;
|
||||
this.female_users = 0;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
PackMsgType,
|
||||
Gender,
|
||||
FansclubType,
|
||||
ShareType,
|
||||
EnterType,
|
||||
FansClubInfo,
|
||||
MsgUser,
|
||||
RoomAnchorInfo,
|
||||
Msg,
|
||||
LikeMsg,
|
||||
GiftMsg,
|
||||
MemberMessage,
|
||||
UserSeqMsg,
|
||||
FansclubMsg,
|
||||
ShareMessage,
|
||||
LiveStats
|
||||
};
|
||||
@@ -0,0 +1,312 @@
|
||||
/**
|
||||
* 抖音直播间消息处理脚本
|
||||
* 消息类型说明:
|
||||
* 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();
|
||||
Generated
+100
@@ -0,0 +1,100 @@
|
||||
{
|
||||
"name": "dy-barrage-grab-nodejs",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "dy-barrage-grab-nodejs",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"chalk": "^4.1.2",
|
||||
"ws": "^8.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-styles": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||
"dependencies": {
|
||||
"color-convert": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/chalk": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz",
|
||||
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
|
||||
"dependencies": {
|
||||
"ansi-styles": "^4.1.0",
|
||||
"supports-color": "^7.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"dependencies": {
|
||||
"color-name": "~1.1.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
|
||||
},
|
||||
"node_modules/has-flag": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-4.0.0.tgz",
|
||||
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/supports-color": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-7.2.0.tgz",
|
||||
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
|
||||
"dependencies": {
|
||||
"has-flag": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/ws": {
|
||||
"version": "8.18.1",
|
||||
"resolved": "https://registry.npmmirror.com/ws/-/ws-8.18.1.tgz",
|
||||
"integrity": "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"bufferutil": "^4.0.1",
|
||||
"utf-8-validate": ">=5.0.2"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"bufferutil": {
|
||||
"optional": true
|
||||
},
|
||||
"utf-8-validate": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "dy-barrage-grab-nodejs",
|
||||
"version": "1.0.0",
|
||||
"description": "抖音直播弹幕获取工具 - Node.js版本",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
"start": "node main.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"ws": "^8.13.0",
|
||||
"chalk": "^4.1.2"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
const {
|
||||
MsgUser, FansClubInfo, Msg, LikeMsg, MemberMessage, UserSeqMsg,
|
||||
Gender, GiftMsg, FansclubMsg, ShareMessage, RoomAnchorInfo, ShareType
|
||||
} = require('./entities');
|
||||
|
||||
/**
|
||||
* 消息解析器类
|
||||
*/
|
||||
class MessageParser {
|
||||
/**
|
||||
* 解析用户数据
|
||||
* @param {Object} userData - 用户数据对象
|
||||
* @returns {MsgUser} - 解析后的用户对象
|
||||
*/
|
||||
static parseUser(userData) {
|
||||
const user = new MsgUser();
|
||||
if (!userData) {
|
||||
return user;
|
||||
}
|
||||
|
||||
user.Id = userData.Id || 0;
|
||||
user.IsAdmin = userData.IsAdmin || false;
|
||||
user.IsAnchor = userData.IsAnchor || false;
|
||||
user.ShortId = userData.ShortId || 0;
|
||||
user.DisplayId = userData.DisplayId || "";
|
||||
user.Nickname = userData.Nickname || "匿名用户";
|
||||
user.Level = userData.Level || 0;
|
||||
user.PayLevel = userData.PayLevel || 0;
|
||||
user.Gender = userData.Gender || 0;
|
||||
user.HeadImgUrl = userData.HeadImgUrl || "";
|
||||
user.SecUid = userData.SecUid || "";
|
||||
user.FollowerCount = userData.FollowerCount || 0;
|
||||
user.FollowStatus = userData.FollowStatus || 0;
|
||||
user.FollowingCount = userData.FollowingCount || 0;
|
||||
|
||||
// 解析粉丝团信息
|
||||
if (userData.FansClub) {
|
||||
const fansClubData = userData.FansClub;
|
||||
user.FansClub = new FansClubInfo({
|
||||
ClubName: fansClubData.ClubName || "",
|
||||
Level: fansClubData.Level || 0
|
||||
});
|
||||
}
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析主播信息
|
||||
* @param {Object} ownerData - 主播数据对象
|
||||
* @returns {RoomAnchorInfo|null} - 解析后的主播信息对象或null
|
||||
*/
|
||||
static parseAnchorInfo(ownerData) {
|
||||
if (!ownerData) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const owner = new RoomAnchorInfo({
|
||||
UserId: ownerData.UserId || "",
|
||||
SecUid: ownerData.SecUid || "",
|
||||
Nickname: ownerData.Nickname || "",
|
||||
HeadUrl: ownerData.HeadUrl || "",
|
||||
FollowStatus: ownerData.FollowStatus || 0
|
||||
});
|
||||
|
||||
return owner;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析通用消息字段
|
||||
* @param {Object} data - 消息数据对象
|
||||
* @param {Msg} msg - 消息对象
|
||||
* @returns {Msg} - 解析后的消息对象
|
||||
*/
|
||||
static parseCommonMsgFields(data, msg) {
|
||||
msg.MsgId = data.MsgId || 0;
|
||||
msg.Content = data.Content || "";
|
||||
msg.RoomId = data.RoomId || "";
|
||||
msg.WebRoomId = data.WebRoomId || "";
|
||||
msg.Appid = data.Appid || "";
|
||||
|
||||
// 解析用户数据
|
||||
if (data.User) {
|
||||
msg.User = MessageParser.parseUser(data.User);
|
||||
}
|
||||
|
||||
// 解析主播数据,优先使用正确拼写的"Owner"
|
||||
if (data.Owner) {
|
||||
msg.Owner = MessageParser.parseAnchorInfo(data.Owner);
|
||||
}
|
||||
// 如果没有"Owner"字段才考虑拼写错误的"Onwer"字段,并且确保不是字符串
|
||||
else if (data.Onwer && typeof data.Onwer !== 'string') {
|
||||
msg.Owner = MessageParser.parseAnchorInfo(data.Onwer);
|
||||
}
|
||||
|
||||
return msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析弹幕消息
|
||||
* @param {Object} data - 弹幕消息数据
|
||||
* @returns {Msg} - 解析后的弹幕消息对象
|
||||
*/
|
||||
static parseDanmaku(data) {
|
||||
const msg = new Msg();
|
||||
return MessageParser.parseCommonMsgFields(data, msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析点赞消息
|
||||
* @param {Object} data - 点赞消息数据
|
||||
* @returns {LikeMsg} - 解析后的点赞消息对象
|
||||
*/
|
||||
static parseLike(data) {
|
||||
const likeMsg = new LikeMsg();
|
||||
MessageParser.parseCommonMsgFields(data, likeMsg);
|
||||
likeMsg.Count = data.Count || 0;
|
||||
likeMsg.Total = data.Total || 0;
|
||||
return likeMsg;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析进入直播间消息
|
||||
* @param {Object} data - 进入直播间消息数据
|
||||
* @returns {MemberMessage} - 解析后的进入直播间消息对象
|
||||
*/
|
||||
static parseMember(data) {
|
||||
const memberMsg = new MemberMessage();
|
||||
MessageParser.parseCommonMsgFields(data, memberMsg);
|
||||
memberMsg.CurrentCount = data.CurrentCount || 0;
|
||||
memberMsg.EnterTipType = data.EnterTipType || 0;
|
||||
return memberMsg;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析礼物消息
|
||||
* @param {Object} data - 礼物消息数据
|
||||
* @returns {GiftMsg} - 解析后的礼物消息对象
|
||||
*/
|
||||
static parseGift(data) {
|
||||
const giftMsg = new GiftMsg();
|
||||
MessageParser.parseCommonMsgFields(data, giftMsg);
|
||||
giftMsg.GiftId = data.GiftId || 0;
|
||||
giftMsg.GiftName = data.GiftName || "";
|
||||
giftMsg.GroupId = data.GroupId || 0;
|
||||
giftMsg.GiftCount = data.GiftCount || 0;
|
||||
giftMsg.RepeatCount = data.RepeatCount || 0;
|
||||
giftMsg.DiamondCount = data.DiamondCount || 0;
|
||||
giftMsg.GiftValue = data.GiftValue || 0; // 兼容原有代码
|
||||
giftMsg.Combo = data.Combo || false;
|
||||
giftMsg.ImgUrl = data.ImgUrl || "";
|
||||
|
||||
// 解析接收礼物的用户
|
||||
if (data.ToUser) {
|
||||
giftMsg.ToUser = MessageParser.parseUser(data.ToUser);
|
||||
}
|
||||
|
||||
return giftMsg;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析直播间统计消息
|
||||
* @param {Object} data - 直播间统计消息数据
|
||||
* @returns {UserSeqMsg} - 解析后的直播间统计消息对象
|
||||
*/
|
||||
static parseStatistics(data) {
|
||||
const statsMsg = new UserSeqMsg();
|
||||
MessageParser.parseCommonMsgFields(data, statsMsg);
|
||||
statsMsg.OnlineUserCount = data.OnlineUserCount || 0;
|
||||
statsMsg.TotalUserCount = data.TotalUserCount || 0;
|
||||
statsMsg.TotalUserCountStr = data.TotalUserCountStr || "0";
|
||||
statsMsg.OnlineUserCountStr = data.OnlineUserCountStr || "0";
|
||||
return statsMsg;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析粉丝团消息
|
||||
* @param {Object} data - 粉丝团消息数据
|
||||
* @returns {FansclubMsg} - 解析后的粉丝团消息对象
|
||||
*/
|
||||
static parseFansclub(data) {
|
||||
const fansclubMsg = new FansclubMsg();
|
||||
MessageParser.parseCommonMsgFields(data, fansclubMsg);
|
||||
fansclubMsg.Type = data.Type || 0;
|
||||
fansclubMsg.Level = data.Level || 0;
|
||||
fansclubMsg.FansClubName = data.FansClubName || "";
|
||||
return fansclubMsg;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析直播间分享消息
|
||||
* @param {Object} data - 直播间分享消息数据
|
||||
* @returns {ShareMessage} - 解析后的直播间分享消息对象
|
||||
*/
|
||||
static parseShare(data) {
|
||||
const shareMsg = new ShareMessage();
|
||||
MessageParser.parseCommonMsgFields(data, shareMsg);
|
||||
shareMsg.ShareType = data.ShareType || ShareType.未知;
|
||||
return shareMsg;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析关注消息
|
||||
* @param {Object} data - 关注消息数据
|
||||
* @returns {Msg} - 解析后的关注消息对象
|
||||
*/
|
||||
static parseFollow(data) {
|
||||
const followMsg = new Msg();
|
||||
return MessageParser.parseCommonMsgFields(data, followMsg);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
MessageParser
|
||||
};
|
||||
Reference in New Issue
Block a user