完善 python 和 node Demo
This commit is contained in:
@@ -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