完善 python 和 node Demo

This commit is contained in:
Muihd
2025-04-30 22:53:19 +08:00
parent 1835f70b23
commit afc1303f11
11 changed files with 1468 additions and 380 deletions
+256
View File
@@ -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
};
+312
View File
@@ -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();
+100
View File
@@ -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
}
}
}
}
}
+13
View File
@@ -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"
}
}
+215
View File
@@ -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
};
-291
View File
@@ -1,291 +0,0 @@
# ruff: noqa: F401,F403,F405,E402,F541,E722
import asyncio
import websockets
import json
import os
import ctypes
import sys
import time
import winreg
import tkinter as tk
from tkinter import ttk
import customtkinter
import subprocess
import random
import threading
import atexit
import psutil
import queue
# 创建一个队列
gui_queue = queue.Queue()
# 创建一个线程安全的标志
stop_flag = threading.Event()
def is_admin():
try:
return ctypes.windll.shell32.IsUserAnAdmin()
except:
return False
def disable_proxy():
internet_settings = winreg.OpenKey(
winreg.HKEY_CURRENT_USER,
r"Software\Microsoft\Windows\CurrentVersion\Internet Settings",
0,
winreg.KEY_ALL_ACCESS,
)
# 设置代理使能为0(禁用)
winreg.SetValueEx(internet_settings, "ProxyEnable", 0, winreg.REG_DWORD, 0)
# 清空代理服务器信息
winreg.SetValueEx(internet_settings, "ProxyServer", 0, winreg.REG_SZ, "")
winreg.CloseKey(internet_settings)
print("Proxy disabled.")
def initGame():
print("Init game.")
pass
class App(customtkinter.CTk):
def __init__(self):
super().__init__()
self.title("dy-barrage-grab-log-window")
self.geometry("800x600")
self.grid_columnconfigure((0, 1), weight=1)
self.grid_rowconfigure(0, weight=1)
self.LogFrame = LogFrame(self)
self.LogFrame.grid(row=0, column=0, sticky="nsew")
class LogFrame(customtkinter.CTkFrame):
def __init__(self, master):
super().__init__(master)
# 创建一个Frame来包含treeview和滚动条
self.tree_frame = ttk.Frame(self)
self.tree_frame.grid(row=0, column=0, sticky="nsew", padx=50, pady=50)
# 创建一个滚动条
self.scrollbar = customtkinter.CTkScrollbar(self.tree_frame)
self.scrollbar.grid(row=0, column=1, sticky="ns")
self.treeview = ttk.Treeview(
self.tree_frame,
columns=("Type", "User ID", "User Nickname", "Content"),
show="headings",
height=20,
yscrollcommand=self.scrollbar.set, # 将滚动条与treeview关联
)
self.treeview.grid(row=0, column=0, sticky="nsew")
# 设置滚动条的命令为移动treeview
self.scrollbar.configure(command=self.treeview.yview)
# 设置列宽
self.treeview.column("Type", width=50)
self.treeview.column("User ID", width=100)
self.treeview.column("User Nickname", width=100)
self.treeview.column("Content", width=450)
self.treeview.heading("Type", text="Type")
self.treeview.heading("User ID", text="User ID")
self.treeview.heading("User Nickname", text="User Nickname")
self.treeview.heading("Content", text="Content")
# 设置不同类型的颜色
self.treeview.tag_configure("type1", foreground="black")
self.treeview.tag_configure("type2", foreground="cyan")
self.treeview.tag_configure("type3", foreground="gold")
self.treeview.tag_configure("type4", foreground="blue")
self.treeview.tag_configure("type5", foreground="red")
self.treeview.tag_configure("type6", foreground="purple")
self.treeview.tag_configure("type7", foreground="green")
self.treeview.tag_configure("type8", foreground="orange")
self.treeview.tag_configure("type9", foreground="gray")
# 添加更多类型的颜色配置...
def display_message(self, message):
parsed_message = json.loads(message)
message_type = parsed_message.get("Type")
data_str = parsed_message.get("Data", "{}")
try:
data = json.loads(data_str)
except json.JSONDecodeError:
print(f"Cannot parse 'Data' as JSON: {data_str}")
return
user = data.get("User", {})
if user is None:
# print(f"'User' is None in 'Data': {data_str}")
user = {"Id": "无", "Nickname": "无"}
user_id = user.get("Id", "")
user_nickname = user.get("Nickname", "")
content = data.get("Content", "")
# 根据类型设置颜色
color_tag = f"type{message_type}"
message_type_str = [
"无",
"弹幕",
"点赞",
"进房",
"关注",
"礼物",
"统计",
"粉团",
"分享",
"下播",
]
self.treeview.insert(
"",
"end",
values=(
message_type_str[message_type],
user_id,
user_nickname,
content,
),
tags=(color_tag,),
)
# async def send_ping(websocket):
# while True:
# try:
# if websocket.open:
# await websocket.ping()
# await asyncio.sleep(5)
# else:
# break
# except websockets.exceptions.ConnectionClosedOK:
# # 连接已关闭,尝试重新连接
# websocket = await websockets.connect("ws://127.0.0.1:8888")
async def receive_messages():
while True:
try:
async with websockets.connect(
"ws://127.0.0.1:8888", ping_timeout=None, ping_interval=None
) as websocket:
while True:
message = await websocket.recv()
if message is not None:
try:
json.loads(message)
app.LogFrame.display_message(message)
except json.JSONDecodeError:
print(
f"Received a message that could not be parsed as JSON: {message}"
)
except (
websockets.ConnectionClosed,
websockets.exceptions.ConnectionClosedError,
websockets.exceptions.ConnectionClosedOK,
) as e:
print(f"Connection closed, retrying...{e}")
continue
except Exception as e:
print(f"An error occurred on ws server: {e}")
continue
def main():
global listener_process
global app
# Check if the process is already running
for proc in psutil.process_iter():
if proc.name() == "WssBarrageServer.exe":
print("The process is already running.")
proc.kill()
time.sleep(1)
# Start a new process
# 将WssBarrageServer.exe放至同目录下dy-barrage-grab文件夹
# 获取当前脚本的绝对路径
script_dir = os.path.dirname(os.path.realpath(__file__))
# 构建WssBarrageServer.exe的绝对路径
exe_path = os.path.join(script_dir, "dy-barrage-grab", "WssBarrageServer.exe")
# 使用绝对路径启动进程
listener_process = subprocess.Popen(exe_path)
# 创建一个新的事件循环
loop = asyncio.new_event_loop()
# 设置这个事件循环为当前线程的事件循环
asyncio.set_event_loop(loop)
app = App()
# 点击关闭按钮时调用的函数
def on_close():
print("Closing...")
# 设置停止标志
stop_flag.set()
# 关闭WssBarrageServer
listener_process.terminate()
# 关闭代理
disable_proxy()
# 退出程序
os._exit(0)
# 设置关闭按钮的回调函数
app.protocol("WM_DELETE_WINDOW", on_close)
# 在事件循环中创建新的异步任务
loop.create_task(receive_messages())
def update():
# 更新Tkinter的界面
app.update()
# 在事件循环中定期调用update方法
loop.call_soon(update)
# 检查队列中是否有任务
while not gui_queue.empty():
# 获取任务并执行
task = gui_queue.get()
task()
# 在事件循环中定期调用update方法
loop.call_soon(update)
try:
# 运行事件循环
loop.run_forever()
except Exception as e:
print(f"An error occurred in loop: {e}")
finally:
# 关闭事件循环
loop.close()
if __name__ == "__main__":
if is_admin():
# 如果已经是管理员权限,那么直接运行你的代码
print("Running as administrator.")
pass
else:
# 如果不是管理员权限,那么以管理员权限重新启动程序
print("Running as non-administrator.")
ctypes.windll.shell32.ShellExecuteW(
None, "runas", sys.executable, " ".join(sys.argv), None, 1
)
# try:
main()
# except Exception as e:
# print(f"An error occurred: {e}")
# finally:
# # 这里结束进程
# listener_process.terminate()
# 注册一个函数,在程序结束时调用
def on_exit():
listener_process.terminate()
atexit.register(on_exit)
+167
View File
@@ -0,0 +1,167 @@
from enum import Enum, IntEnum
from typing import Optional, Dict, Any, List, Union
from dataclasses import dataclass, field
# 枚举类型定义
class PackMsgType(IntEnum):
"""消息类型枚举"""
无 = 0
弹幕消息 = 1
点赞消息 = 2
进直播间 = 3
关注消息 = 4
礼物消息 = 5
直播间统计 = 6
粉丝团消息 = 7
直播间分享 = 8
下播 = 9
class Gender(IntEnum):
"""性别枚举"""
未知 = 0
男 = 1
女 = 2
def to_string(self) -> str:
"""将性别枚举转换为中文字符串"""
if self == Gender.男:
return "男"
elif self == Gender.女:
return "女"
else:
return "妖"
class FansclubType(IntEnum):
"""粉丝团消息类型"""
无 = 0
粉丝团升级 = 1
加入粉丝团 = 2
class ShareType(IntEnum):
"""直播间分享目标"""
未知 = 0
微信 = 1
朋友圈 = 2
微博 = 3
QQ空间 = 4
QQ = 5
抖音好友 = 112
class EnterType(IntEnum):
"""观众的进入方式"""
正常进入 = 0
通过分享进入 = 6
# 数据类定义
@dataclass
class FansClubInfo:
"""粉丝团信息"""
ClubName: str = ""
Level: int = 0
@dataclass
class MsgUser:
"""用户信息"""
Id: int = 0
IsAdmin: bool = False
IsAnchor: bool = False
ShortId: int = 0
DisplayId: str = ""
Nickname: str = "匿名用户"
Level: int = 0
PayLevel: int = 0
Gender: Gender = Gender.未知
HeadImgUrl: str = ""
SecUid: str = ""
FansClub: Optional[FansClubInfo] = None
FollowerCount: int = 0
FollowStatus: int = 0
FollowingCount: int = 0
def gender_to_string(self) -> str:
"""返回性别的中文表示"""
return Gender(self.Gender).to_string()
@dataclass
class RoomAnchorInfo:
"""直播间主播信息"""
UserId: str = ""
SecUid: str = ""
Nickname: str = ""
HeadUrl: str = ""
FollowStatus: int = 0
@dataclass
class Msg:
"""基础消息类"""
MsgId: int = 0
User: MsgUser = field(default_factory=MsgUser)
Owner: Optional[RoomAnchorInfo] = None # 修正字段名称
Content: str = ""
RoomId: str = ""
WebRoomId: str = ""
RoomTitle: str = ""
IsAnonymous: bool = False
Appid: str = ""
@dataclass
class LikeMsg(Msg):
"""点赞消息"""
Count: int = 0
Total: int = 0
@dataclass
class GiftMsg(Msg):
"""礼物消息"""
GiftId: int = 0
GiftName: str = ""
GroupId: int = 0
GiftCount: int = 0
RepeatCount: int = 0
DiamondCount: int = 0 # 抖币价格
Combo: bool = False # 该礼物是否可连击
ImgUrl: str = "" # 礼物图片地址
ToUser: Optional[MsgUser] = None # 送礼目标(连麦直播间有用)
GiftValue: int = 0 # 在原Python代码中使用的礼物价值字段
@dataclass
class MemberMessage(Msg):
"""进入直播间消息"""
CurrentCount: int = 0
EnterTipType: int = 0
@dataclass
class UserSeqMsg(Msg):
"""直播间统计消息"""
OnlineUserCount: int = 0
TotalUserCount: int = 0
TotalUserCountStr: str = "0"
OnlineUserCountStr: str = "0"
@dataclass
class FansclubMsg(Msg):
"""粉丝团消息"""
Type: int = 0 # 粉丝团消息类型,升级1,加入2
Level: int = 0 # 粉丝团等级
FansClubName: str = "" # 粉丝团名称
@dataclass
class ShareMessage(Msg):
"""直播间分享"""
ShareType: ShareType = ShareType.未知
# 全局统计数据类
@dataclass
class LiveStats:
"""直播间统计数据"""
total_likes: int = 0
total_users: int = 0
male_users: int = 0
female_users: int = 0
def reset(self) -> None:
"""重置统计数据"""
self.total_likes = 0
self.total_users = 0
self.male_users = 0
self.female_users = 0
+245
View File
@@ -0,0 +1,245 @@
import asyncio
import websockets
import json
import time
from datetime import datetime
from typing import Dict, Any
from colorama import init, Fore, Style
from entities import (
PackMsgType, Gender, LiveStats, LikeMsg, MemberMessage, UserSeqMsg,
GiftMsg, FansclubMsg, ShareMessage, ShareType
)
from parsers import MessageParser
'''
抖音直播间消息处理脚本
消息类型说明:
1[普通弹幕]|2[点赞消息]|3[进入直播间]|4[关注消息]|5[礼物消息]|6[统计消息]|7[粉丝团消息]|8[直播间分享]|9[下播]
全局统计变量:
Live_Total - 本场点赞数
Live_user - 本场总观看人数
Live_man - 本场男生人数
Live_woman - 本场女生人数
'''
# 初始化彩色输出
init(autoreset=True)
# 颜色定义
COLOR_MAP = {
PackMsgType.弹幕消息: Fore.CYAN, # 青色
PackMsgType.点赞消息: Fore.YELLOW, # 黄色
PackMsgType.进直播间: Fore.GREEN, # 绿色
PackMsgType.关注消息: Fore.MAGENTA, # 洋红色
PackMsgType.礼物消息: Fore.RED, # 红色
PackMsgType.直播间统计: Fore.BLUE, # 蓝色
PackMsgType.粉丝团消息: Fore.LIGHTBLUE_EX, # 浅蓝色
PackMsgType.直播间分享: Fore.LIGHTMAGENTA_EX, # 浅洋红色
PackMsgType.下播: Fore.RED, # 红色
}
# 初始化统计对象
live_stats = LiveStats()
def get_timestamp() -> str:
"""返回当前时间戳,格式:HH:MM:SS"""
return datetime.now().strftime("%H:%M:%S")
def print_colored_message(msg_type: PackMsgType, content: str, author: str = "未知") -> None:
"""打印带颜色的消息"""
color = COLOR_MAP.get(msg_type, Fore.WHITE)
timestamp = get_timestamp()
elapsed_time = "["+author+"]"
print(f"{color}{timestamp} {elapsed_time} {content}{Style.RESET_ALL}")
def get_msg_owner(msg) -> str:
"""统一获取消息所属的主播/房间信息"""
if hasattr(msg, 'Owner') and msg.Owner:
return msg.Owner.Nickname
elif hasattr(msg, 'WebRoomId') and msg.WebRoomId:
return msg.WebRoomId
elif hasattr(msg, 'RoomId') and msg.RoomId:
return msg.RoomId
return "未知"
# 处理函数:弹幕消息
def handle_danmaku(data_dict: Dict[str, Any]) -> None:
"""处理用户发送的弹幕消息"""
msg = MessageParser.parse_danmaku(data_dict)
gender_str = msg.User.gender_to_string()
content = f"[弹幕消息] [{gender_str}] {msg.User.Nickname}: {msg.Content}"
author = get_msg_owner(msg)
print_colored_message(PackMsgType.弹幕消息, content, author)
# 处理函数:点赞消息
def handle_dianzanku(data_dict: Dict[str, Any]) -> None:
"""处理用户点赞消息并更新全局统计"""
like_msg = MessageParser.parse_like(data_dict)
live_stats.total_likes = like_msg.Total
gender_str = like_msg.User.gender_to_string()
content = f"[点赞消息] [{gender_str}] {like_msg.User.Nickname} 为主播点了{like_msg.Count}个赞, 总点赞{like_msg.Total}"
author = get_msg_owner(like_msg)
print_colored_message(PackMsgType.点赞消息, content, author)
# 处理函数:进入直播间
def handle_userentry(data_dict: Dict[str, Any]) -> None:
"""处理用户进入直播间的消息,并根据性别更新统计"""
member_msg = MessageParser.parse_member(data_dict)
# 根据性别更新全局计数
if member_msg.User.Gender == Gender.男:
live_stats.male_users += 1
elif member_msg.User.Gender == Gender.女:
live_stats.female_users += 1
gender_str = member_msg.User.gender_to_string()
content = f"[进直播间] [{gender_str}] {member_msg.User.Nickname} $来了 直播间人数:{member_msg.CurrentCount}"
author = get_msg_owner(member_msg)
print_colored_message(PackMsgType.进直播间, content, author)
# 处理函数:关注消息
def handle_follow(data_dict: Dict[str, Any]) -> None:
"""处理用户关注主播的消息"""
follow_msg = MessageParser.parse_follow(data_dict)
gender_str = follow_msg.User.gender_to_string()
content = f"[关注消息] [{gender_str}] {follow_msg.User.Nickname} 关注了主播"
author = get_msg_owner(follow_msg)
print_colored_message(PackMsgType.关注消息, content, author)
# 处理函数:礼物消息
def handle_gift(data_dict: Dict[str, Any]) -> None:
"""处理用户赠送礼物的消息"""
gift_msg = MessageParser.parse_gift(data_dict)
gender_str = gift_msg.User.gender_to_string()
# 使用DiamondCount或GiftValue作为礼物价值
gift_value = gift_msg.DiamondCount or gift_msg.GiftValue
content = f"[礼物消息] [{gender_str}] {gift_msg.User.Nickname} 送出了 {gift_msg.GiftCount}个{gift_msg.GiftName}, 价值{gift_value}抖币"
author = get_msg_owner(gift_msg)
print_colored_message(PackMsgType.礼物消息, content, author)
# 处理函数:统计消息
def handle_statistics(data_dict: Dict[str, Any]) -> None:
"""处理直播间统计信息"""
stats_msg = MessageParser.parse_statistics(data_dict)
live_stats.total_users = int(stats_msg.TotalUserCount)
content = f"[直播间统计] 当前直播间人数 {stats_msg.OnlineUserCountStr}, 累计直播间人数 {stats_msg.TotalUserCountStr}万"
author = get_msg_owner(stats_msg)
print_colored_message(PackMsgType.直播间统计, content, author)
# 处理函数:粉丝团消息
def handle_fansclub(data_dict: Dict[str, Any]) -> None:
"""处理粉丝团相关消息"""
fansclub_msg = MessageParser.parse_fansclub(data_dict)
gender_str = fansclub_msg.User.gender_to_string()
if fansclub_msg.Type == 1:
action = "升级了"
content = f"[粉丝团消息] [{gender_str}] {fansclub_msg.User.Nickname} {action}{fansclub_msg.FansClubName}粉丝团到{fansclub_msg.Level}级"
else:
action = "加入了"
content = f"[粉丝团消息] [{gender_str}] {fansclub_msg.User.Nickname} {action}{fansclub_msg.FansClubName}粉丝团"
author = get_msg_owner(fansclub_msg)
print_colored_message(PackMsgType.粉丝团消息, content, author)
# 处理函数:直播间分享
def handle_share(data_dict: Dict[str, Any]) -> None:
"""处理用户分享直播间的消息"""
share_msg = MessageParser.parse_share(data_dict)
gender_str = share_msg.User.gender_to_string()
# 获取分享目标类型
share_type_str = share_msg.ShareType.name if hasattr(share_msg.ShareType, 'name') else "未知平台"
content = f"[直播间分享] [{gender_str}] {share_msg.User.Nickname} 分享了直播间到{share_type_str}"
author = get_msg_owner(share_msg)
print_colored_message(PackMsgType.直播间分享, content, author)
# 处理函数:直播结束
def handle_live_exit() -> None:
"""处理直播结束事件,显示并重置统计数据"""
content = f"[下播] 直播结束:累计观看 {live_stats.total_users} 人,累计点赞 {live_stats.total_likes},"\
f"男生 {live_stats.male_users} 女生 {live_stats.female_users}"
print_colored_message(PackMsgType.下播, content, "系统消息")
live_stats.reset()
print(f"直播统计数据已清空~")
async def process_message(message: str) -> None:
"""处理从WebSocket接收到的单条消息"""
try:
data = json.loads(message)
if "Type" not in data:
print("⚠️ 收到无效消息:缺少Type字段")
return
msg_type = PackMsgType(data['Type'])
# 检查是否需要处理Data字段
if msg_type == PackMsgType.下播:
handle_live_exit()
return
if "Data" not in data:
print(f"⚠️ 消息类型 {msg_type.name} 缺少Data字段")
return
try:
data_dict = json.loads(data['Data'])
# 消息处理映射表
handlers = {
PackMsgType.弹幕消息: handle_danmaku,
PackMsgType.点赞消息: handle_dianzanku,
PackMsgType.进直播间: handle_userentry,
PackMsgType.关注消息: handle_follow,
PackMsgType.礼物消息: handle_gift,
PackMsgType.直播间统计: handle_statistics,
PackMsgType.粉丝团消息: handle_fansclub,
PackMsgType.直播间分享: handle_share,
}
# 分发消息到相应处理器
if msg_type in handlers:
handlers[msg_type](data_dict)
except json.JSONDecodeError:
print(f"⚠️ 无法解析Data字段内容:{data['Data'][:100]}...")
except json.JSONDecodeError:
print(f"⚠️ 无效的JSON格式: {message[:100]}...")
except Exception as e:
print(f"❌ 消息处理错误: {str(e)}")
async def connect_and_print() -> None:
"""连接到WebSocket服务器并处理接收到的消息"""
uri = "ws://127.0.0.1:8888"
while True:
try:
print(f"正在连接到服务器 {uri}...")
async with websockets.connect(uri, ping_interval=None) as websocket:
print(f"✅ 已成功连接到服务器 {uri}")
while True:
message = await websocket.recv()
await process_message(message)
except websockets.exceptions.ConnectionClosed as e:
print(f"⚠️ 与服务器的连接已关闭: {e}")
except ConnectionRefusedError:
print(f"❌ 无法连接到服务器 {uri},5秒后重试...")
except Exception as e:
print(f"❌ 发生错误: {str(e)}")
# 连接断开后等待重连
await asyncio.sleep(5)
print("正在尝试重新连接...")
# 程序入口
if __name__ == "__main__":
print("启动抖音直播消息监听...")
asyncio.run(connect_and_print())
+158
View File
@@ -0,0 +1,158 @@
from typing import Dict, Any, Optional
from entities import (
MsgUser, FansClubInfo, Msg, LikeMsg, MemberMessage, UserSeqMsg,
Gender, GiftMsg, FansclubMsg, ShareMessage, RoomAnchorInfo, ShareType
)
# 消息解析器
class MessageParser:
@staticmethod
def parse_user(user_data: Dict[str, Any]) -> MsgUser:
"""解析用户数据"""
user = MsgUser()
if not user_data:
return user
user.Id = user_data.get("Id", 0)
user.IsAdmin = user_data.get("IsAdmin", False)
user.IsAnchor = user_data.get("IsAnchor", False)
user.ShortId = user_data.get("ShortId", 0)
user.DisplayId = user_data.get("DisplayId", "")
user.Nickname = user_data.get("Nickname", "匿名用户")
user.Level = user_data.get("Level", 0)
user.PayLevel = user_data.get("PayLevel", 0)
user.Gender = Gender(user_data.get("Gender", 0))
user.HeadImgUrl = user_data.get("HeadImgUrl", "")
user.SecUid = user_data.get("SecUid", "")
user.FollowerCount = user_data.get("FollowerCount", 0)
user.FollowStatus = user_data.get("FollowStatus", 0)
user.FollowingCount = user_data.get("FollowingCount", 0)
# 解析粉丝团信息
if "FansClub" in user_data and user_data["FansClub"]:
fans_club_data = user_data["FansClub"]
user.FansClub = FansClubInfo(
ClubName=fans_club_data.get("ClubName", ""),
Level=fans_club_data.get("Level", 0)
)
return user
@staticmethod
def parse_anchor_info(owner_data: Dict[str, Any]) -> Optional[RoomAnchorInfo]:
"""解析主播信息"""
if not owner_data:
return None
owner = RoomAnchorInfo()
owner.UserId = owner_data.get("UserId", "")
owner.SecUid = owner_data.get("SecUid", "")
owner.Nickname = owner_data.get("Nickname", "")
owner.HeadUrl = owner_data.get("HeadUrl", "")
owner.FollowStatus = owner_data.get("FollowStatus", 0)
return owner
@staticmethod
def parse_common_msg_fields(data: Dict[str, Any], msg: Msg) -> Msg:
"""解析通用消息字段"""
msg.MsgId = data.get("MsgId", 0)
msg.Content = data.get("Content", "")
msg.RoomId = data.get("RoomId", "")
msg.WebRoomId = data.get("WebRoomId", "")
msg.RoomTitle = data.get("RoomTitle", "")
msg.IsAnonymous = data.get("IsAnonymous", False)
msg.Appid = data.get("Appid", "")
# 解析用户数据
if "User" in data and data["User"]:
msg.User = MessageParser.parse_user(data["User"])
# 解析主播数据,优先使用正确拼写的"Owner"
if "Owner" in data and data["Owner"]:
msg.Owner = MessageParser.parse_anchor_info(data["Owner"])
# 如果没有"Owner"字段才考虑拼写错误的"Onwer"字段,并且确保不是字符串
elif "Onwer" in data and data["Onwer"] and not isinstance(data["Onwer"], str):
msg.Owner = MessageParser.parse_anchor_info(data["Onwer"])
return msg
@staticmethod
def parse_danmaku(data: Dict[str, Any]) -> Msg:
"""解析弹幕消息"""
msg = Msg()
return MessageParser.parse_common_msg_fields(data, msg)
@staticmethod
def parse_like(data: Dict[str, Any]) -> LikeMsg:
"""解析点赞消息"""
like_msg = LikeMsg()
MessageParser.parse_common_msg_fields(data, like_msg)
like_msg.Count = data.get("Count", 0)
like_msg.Total = data.get("Total", 0)
return like_msg
@staticmethod
def parse_member(data: Dict[str, Any]) -> MemberMessage:
"""解析进入直播间消息"""
member_msg = MemberMessage()
MessageParser.parse_common_msg_fields(data, member_msg)
member_msg.CurrentCount = data.get("CurrentCount", 0)
member_msg.EnterTipType = data.get("EnterTipType", 0)
return member_msg
@staticmethod
def parse_gift(data: Dict[str, Any]) -> GiftMsg:
"""解析礼物消息"""
gift_msg = GiftMsg()
MessageParser.parse_common_msg_fields(data, gift_msg)
gift_msg.GiftId = data.get("GiftId", 0)
gift_msg.GiftName = data.get("GiftName", "")
gift_msg.GroupId = data.get("GroupId", 0)
gift_msg.GiftCount = data.get("GiftCount", 0)
gift_msg.RepeatCount = data.get("RepeatCount", 0)
gift_msg.DiamondCount = data.get("DiamondCount", 0)
gift_msg.GiftValue = data.get("GiftValue", 0) # 兼容原有代码
gift_msg.Combo = data.get("Combo", False)
gift_msg.ImgUrl = data.get("ImgUrl", "")
# 解析接收礼物的用户
if "ToUser" in data and data["ToUser"]:
gift_msg.ToUser = MessageParser.parse_user(data["ToUser"])
return gift_msg
@staticmethod
def parse_statistics(data: Dict[str, Any]) -> UserSeqMsg:
"""解析直播间统计消息"""
stats_msg = UserSeqMsg()
MessageParser.parse_common_msg_fields(data, stats_msg)
stats_msg.OnlineUserCount = data.get("OnlineUserCount", 0)
stats_msg.TotalUserCount = data.get("TotalUserCount", 0)
stats_msg.TotalUserCountStr = data.get("TotalUserCountStr", "0")
stats_msg.OnlineUserCountStr = data.get("OnlineUserCountStr", "0")
return stats_msg
@staticmethod
def parse_fansclub(data: Dict[str, Any]) -> FansclubMsg:
"""解析粉丝团消息"""
fansclub_msg = FansclubMsg()
MessageParser.parse_common_msg_fields(data, fansclub_msg)
fansclub_msg.Type = data.get("Type", 0)
fansclub_msg.Level = data.get("Level", 0)
fansclub_msg.FansClubName = data.get("FansClubName", "")
return fansclub_msg
@staticmethod
def parse_share(data: Dict[str, Any]) -> ShareMessage:
"""解析直播间分享消息"""
share_msg = ShareMessage()
MessageParser.parse_common_msg_fields(data, share_msg)
share_msg.ShareType = ShareType(data.get("ShareType", 0))
return share_msg
@staticmethod
def parse_follow(data: Dict[str, Any]) -> Msg:
"""解析关注消息"""
follow_msg = Msg()
return MessageParser.parse_common_msg_fields(data, follow_msg)
+2
View File
@@ -0,0 +1,2 @@
websockets>=10.0
colorama>=0.4.4
-89
View File
@@ -1,89 +0,0 @@
import asyncio
import json
from jsonpath import jsonpath
import websockets
# 1用户发言#2用户点赞#3用户入房#4关注主播#5用户礼物#6人数统计
'''
str(fid[0:2])=="403": #字符串提取方便自定义命令
'''
def msg(data):
global close
load_json_data = json.loads(data.get("Data"))
用户名 = jsonpath(load_json_data, '$.User.Nickname')
用户等级 = jsonpath(load_json_data, '$.User.PayLevel')
粉丝团 = jsonpath(load_json_data, '$.User.FansClub.ClubName')
用户发言 = jsonpath(load_json_data, '$.Content')
if 用户发言[0] == "#关闭":
close = False
else:
print(用户名[0])
print(用户等级[0])
def praise(data): # type2
load_json_data = json.loads(data.get("Data"))
用户点赞 = jsonpath(load_json_data, '$.Content')
print("感谢" + 用户点赞[0])
def welcome(data): # type3
load_json_data = json.loads(data.get("Data"))
用户名 = jsonpath(load_json_data, '$.User.Nickname')
粉丝团 = jsonpath(load_json_data, '$.User.FansClub.ClubName')
if 粉丝团[0] == "Reset":
print("欢迎粉丝:" + 用户名[0])
else:
print("欢迎:" + 用户名[0])
def thank(data): # type5
load_json_data = json.loads(data.get("Data"))
粉丝团 = jsonpath(load_json_data, '$.User.FansClub.ClubName')
用户送礼 = jsonpath(load_json_data, '$.Content')
# txt1 = txt.replace("主播", "黎公子") #字符串替换功能方便拓展功能
if 粉丝团[0] == "Reset":
print("感谢老板:" + 用户送礼[0])
else:
print("感谢:" + 用户送礼[0])
def check(data): # type6
load_json_data = json.loads(data.get("Data"))
房间统计 = jsonpath(load_json_data, '$.Content')
print(房间统计[0])
def check_json(json_data):
Token = json_data.get("Type") # 标签类型
if Token == 1: # 1用户发言
msg(json_data)
elif Token == 2: # 2用户点赞
praise(json_data)
elif Token == 3: # 3用户入房
welcome(json_data)
elif Token == 4: # 感谢关注
print(str(json_data))
elif Token == 5: # 5用户礼物
thank(json_data)
elif Token == 6: # 6人数统计
check(json_data)
else:
print(json_data)
async def main():
global close
async with websockets.connect("ws://127.0.0.1:8888/", ping_interval=None) as ws:
await ws.send("token")
close = True
while close is True:
result = await ws.recv()
check_json(json.loads(result))
await ws.close()
close = True
asyncio.run(main())