完善 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
|
||||||
|
};
|
||||||
@@ -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)
|
|
||||||
@@ -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
|
||||||
@@ -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())
|
||||||
@@ -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)
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
websockets>=10.0
|
||||||
|
colorama>=0.4.4
|
||||||
@@ -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())
|
|
||||||
Reference in New Issue
Block a user