This commit is contained in:
2026-08-04 00:10:33 +08:00
parent c9343da287
commit 6601f14db6
26 changed files with 4370 additions and 274 deletions
+405
View File
@@ -0,0 +1,405 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Text.RegularExpressions;
using BarrageGrab.Modles.BiliProtoEntity;
using BarrageGrab.Proxy.ProxyEventArgs;
using BrotliSharpLib;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace BarrageGrab
{
/// <summary>
/// 哔哩哔哩弹幕抓取器
/// 协议格式: 自定义16字节Big-Endian帧头 + JSON body (body可能为zlib/brotli压缩)
/// </summary>
public class BiliBarrageGrab
{
// B站直播弹幕WS域名
// broadcast-msg.chat.bilibili.com (主域名)
// *-live-comet.chat.bilibili.com (新版comet服务器, 如 zj-cn-live-comet.chat.bilibili.com)
// live-trace.com (备用)
// ws.live.bilibili.com (历史域名)
private readonly Regex biliWsReg = new Regex(@"chat\.bilibili\.com|live-trace\.com|ws\.live\.bilibili\.com", RegexOptions.IgnoreCase);
private const string BILI_LIVE_HOST = "live.bilibili.com";
/// <summary>弹幕消息</summary>
public event EventHandler<BiliRoomMessageEventArgs<BiliDanmuMsg>> OnChatMessage;
/// <summary>点赞消息</summary>
public event EventHandler<BiliRoomMessageEventArgs<BiliLikeData>> OnLikeMessage;
/// <summary>礼物消息</summary>
public event EventHandler<BiliRoomMessageEventArgs<BiliGiftData>> OnGiftMessage;
/// <summary>进入直播间/关注消息</summary>
public event EventHandler<BiliRoomMessageEventArgs<BiliInteractData>> OnInteractMessage;
/// <summary>房间统计消息</summary>
public event EventHandler<BiliRoomMessageEventArgs<BiliRoomStats>> OnRoomStatsMessage;
/// <summary>分享消息 (INTERACT_WORD中msg_type=3)</summary>
public event EventHandler<BiliRoomMessageEventArgs<BiliInteractData>> OnShareMessage;
public BiliBarrageGrab()
{
}
/// <summary>
/// 当前认证的房间ID (从AUTH包解析,供消息处理器读取)
/// </summary>
public string CurrentRoomId { get; private set; } = "";
public bool IsBilibiliHost(string hostname)
{
if (string.IsNullOrWhiteSpace(hostname)) return false;
hostname = hostname.Trim().ToLower();
return biliWsReg.IsMatch(hostname) || hostname == BILI_LIVE_HOST;
}
public bool IsBilibiliWebSocket(string uri)
{
if (string.IsNullOrWhiteSpace(uri)) return false;
return biliWsReg.IsMatch(uri);
}
/// <summary>
/// 处理代理收到的B站WS数据
/// </summary>
public void HandleWebSocketData(WsMessageEventArgs e)
{
var buff = e.Payload;
if (buff == null || buff.Length == 0) return;
try
{
// 一个WS帧可能包含多个B站数据包,需要循环解析
ProcessBuffer(buff, 0, buff.Length, e.ProcessName);
}
catch (Exception ex)
{
if (biliErrCount < 3)
{
Logger.LogError(ex, $"处理B站弹幕数据包时出错:{ex.Message}");
biliErrCount++;
}
if (biliErrCount == 3)
{
Logger.LogWarn("B站弹幕解析持续失败,已抑制后续错误日志");
biliErrCount++;
}
}
}
/// <summary>
/// 处理客户端发送的B站WS数据 (解析AUTH认证包提取roomid)
/// B站客户端连接WS后会发送AUTH包: {uid,roomid,protover,platform,type,key}
/// </summary>
public void HandleSentData(WsMessageEventArgs e)
{
var buff = e.Payload;
if (buff == null || buff.Length < BiliSocketFrame.HEADER_SIZE) return;
try
{
BiliSocketFrame frame;
if (!BiliSocketFrame.TryParse(buff, 0, buff.Length, out frame)) return;
// 只关注认证请求包 (Operation=7)
if (frame.Operation != BiliOperation.AUTH) return;
var payload = BiliSocketFrame.GetPayload(buff, 0, frame);
if (payload == null || payload.Length == 0) return;
var json = System.Text.Encoding.UTF8.GetString(payload);
var jobj = JObject.Parse(json);
var roomIdToken = jobj["roomid"];
if (roomIdToken == null) return;
// roomid可能是整数或字符串
string roomId = roomIdToken.Type == JTokenType.Integer
? roomIdToken.Value<long>().ToString()
: roomIdToken.Value<string>() ?? "";
if (!string.IsNullOrWhiteSpace(roomId))
{
CurrentRoomId = roomId;
Logger.LogInfo($"B站认证包解析成功,当前房间ID: {roomId}");
}
}
catch (Exception ex)
{
Logger.LogWarn($"解析B站AUTH认证包失败: {ex.Message}");
}
}
private static int biliErrCount = 0;
/// <summary>
/// 递归解析缓冲区中可能包含的多个B站数据包
/// </summary>
private void ProcessBuffer(byte[] buffer, int offset, int count, string processName)
{
int processed = 0;
while (processed + BiliSocketFrame.HEADER_SIZE <= count)
{
BiliSocketFrame frame;
if (!BiliSocketFrame.TryParse(buffer, offset + processed, count - processed, out frame))
{
break;
}
// 包长超过剩余缓冲区,说明数据不完整,等下次再处理
if (processed + frame.PacketLength > count)
{
break;
}
var payload = BiliSocketFrame.GetPayload(buffer, offset + processed, frame);
processed += (int)frame.PacketLength;
// 只处理服务器推送的消息和心跳回复
if (frame.Operation == BiliOperation.HEARTBEAT_REPLY)
{
// 心跳回复: body是4字节big-endian人气值
if (payload != null && payload.Length >= 4)
{
var online = (long)BiliSocketFrame.ReadUInt32BE(payload, 0);
var stats = new BiliRoomStats { Online = online, Watched = 0 };
OnRoomStatsMessage?.Invoke(this, new BiliRoomMessageEventArgs<BiliRoomStats>(processName, stats));
}
continue;
}
if (frame.Operation != BiliOperation.MESSAGE) continue;
// 根据协议版本解压
byte[] jsonBytes;
switch (frame.ProtocolVersion)
{
case BiliProtocolVersion.PLAIN:
case BiliProtocolVersion.PLAIN_AUTH:
jsonBytes = payload;
break;
case BiliProtocolVersion.ZLIB:
jsonBytes = ZlibDecompress(payload);
break;
case BiliProtocolVersion.BROTLI:
jsonBytes = BrotliDecompress(payload);
break;
default:
jsonBytes = payload;
break;
}
if (jsonBytes == null || jsonBytes.Length == 0) continue;
// 解压后可能仍然是多个数据包 (Zlib/Brotli压缩的通常是合并包)
if (frame.ProtocolVersion == BiliProtocolVersion.ZLIB ||
frame.ProtocolVersion == BiliProtocolVersion.BROTLI)
{
ProcessBuffer(jsonBytes, 0, jsonBytes.Length, processName);
continue;
}
// 明文JSON: 解析为消息
DispatchJsonMessage(jsonBytes, processName);
}
}
/// <summary>
/// 将JSON字节解析为B站消息并触发对应事件
/// </summary>
private void DispatchJsonMessage(byte[] jsonBytes, string processName)
{
string json;
try
{
json = System.Text.Encoding.UTF8.GetString(jsonBytes);
}
catch { return; }
if (string.IsNullOrWhiteSpace(json)) return;
JObject jobj;
try
{
jobj = JObject.Parse(json);
}
catch { return; }
var cmdToken = jobj["cmd"];
if (cmdToken == null) return;
string cmd = cmdToken.Value<string>();
if (string.IsNullOrWhiteSpace(cmd)) return;
// 兼容cmd带数字后缀的版本,如 DANMU_MSG-4b1cb6f3
int dashIdx = cmd.IndexOf('-');
if (dashIdx > 0) cmd = cmd.Substring(0, dashIdx);
switch (cmd)
{
case BiliCmd.DANMU_MSG:
{
var msg = ParseDanmuMsg(jobj);
if (msg != null)
{
OnChatMessage?.Invoke(this, new BiliRoomMessageEventArgs<BiliDanmuMsg>(processName, msg));
}
break;
}
case BiliCmd.SEND_GIFT:
{
var msg = jobj["data"]?.ToObject<BiliGiftData>();
if (msg != null)
{
OnGiftMessage?.Invoke(this, new BiliRoomMessageEventArgs<BiliGiftData>(processName, msg));
}
break;
}
case BiliCmd.INTERACT_WORD:
{
var msg = jobj["data"]?.ToObject<BiliInteractData>();
if (msg != null)
{
// msg_type: 1=进入 2=关注 3=分享
if (msg.MsgType == 3)
{
OnShareMessage?.Invoke(this, new BiliRoomMessageEventArgs<BiliInteractData>(processName, msg));
}
else
{
OnInteractMessage?.Invoke(this, new BiliRoomMessageEventArgs<BiliInteractData>(processName, msg));
}
}
break;
}
case BiliCmd.LIKE_INFO_V3_CLICK:
{
var msg = jobj["data"]?.ToObject<BiliLikeData>();
if (msg != null)
{
OnLikeMessage?.Invoke(this, new BiliRoomMessageEventArgs<BiliLikeData>(processName, msg));
}
break;
}
case BiliCmd.ONLINE_RANK_COUNT:
{
var count = jobj["data"]?["count"]?.Value<long>() ?? 0;
var stats = new BiliRoomStats { Online = count, Watched = 0 };
OnRoomStatsMessage?.Invoke(this, new BiliRoomMessageEventArgs<BiliRoomStats>(processName, stats));
break;
}
case BiliCmd.WATCHED_CHANGE:
{
var num = jobj["data"]?["num"]?.Value<long>() ?? 0;
var stats = new BiliRoomStats { Online = 0, Watched = num };
OnRoomStatsMessage?.Invoke(this, new BiliRoomMessageEventArgs<BiliRoomStats>(processName, stats));
break;
}
case BiliCmd.ROOM_REAL_TIME_MESSAGE_UPDATE:
{
// 同时包含在线人数和看过人数
var online = jobj["data"]?["online"]?.Value<long>() ?? 0;
var watched = jobj["data"]?["watched_show"]?["num"]?.Value<long>() ?? 0;
var stats = new BiliRoomStats { Online = online, Watched = watched };
OnRoomStatsMessage?.Invoke(this, new BiliRoomMessageEventArgs<BiliRoomStats>(processName, stats));
break;
}
default:
// 未知消息类型忽略
break;
}
}
/// <summary>
/// 解析DANMU_MSG消息: info[1]=内容, info[2]=[uid,uname,isAdmin,rank,?anchor]
/// </summary>
private BiliDanmuMsg ParseDanmuMsg(JObject jobj)
{
try
{
var info = jobj["info"] as JArray;
if (info == null || info.Count < 3) return null;
var content = info[1]?.Value<string>() ?? "";
var userInfo = info[2] as JArray;
if (userInfo == null || userInfo.Count < 2) return null;
var msg = new BiliDanmuMsg
{
Content = content,
Uid = userInfo[0]?.Value<long>() ?? 0,
Uname = userInfo[1]?.Value<string>() ?? "",
IsAdmin = userInfo.Count > 2 && userInfo[2]?.Value<int>() == 1,
IsAnchor = userInfo.Count > 4 && userInfo[4] != null && userInfo[4].Type != JTokenType.Null
};
return msg;
}
catch
{
return null;
}
}
/// <summary>Zlib解压</summary>
private byte[] ZlibDecompress(byte[] data)
{
if (data == null || data.Length == 0) return null;
try
{
using (var ms = new MemoryStream(data))
using (var zlib = new DeflateStream(ms, CompressionMode.Decompress))
using (var outMs = new MemoryStream())
{
// zlib前2字节是header(0x78 0x9c等),DeflateStream需要跳过
// 但 .NET Core 2.1+ 的 DeflateStream 可以处理 zlib header, .NET Framework 需要跳过
if (data.Length >= 2 && data[0] == 0x78)
{
ms.Position = 2;
}
zlib.CopyTo(outMs);
return outMs.ToArray();
}
}
catch (Exception ex)
{
Logger.LogWarn($"B站Zlib解压失败: {ex.Message}");
return null;
}
}
/// <summary>Brotli解压 (使用 BrotliSharpLib)</summary>
private byte[] BrotliDecompress(byte[] data)
{
if (data == null || data.Length == 0) return null;
try
{
using (var ms = new MemoryStream(data))
using (var brotli = new BrotliSharpLib.BrotliStream(ms, CompressionMode.Decompress))
using (var outMs = new MemoryStream())
{
brotli.CopyTo(outMs);
return outMs.ToArray();
}
}
catch (Exception ex)
{
Logger.LogWarn($"B站Brotli解压失败: {ex.Message}");
return null;
}
}
}
/// <summary>B站消息事件参数</summary>
public class BiliRoomMessageEventArgs<T> : EventArgs where T : class
{
public string Process { get; set; }
public T Message { get; set; }
public BiliRoomMessageEventArgs() { }
public BiliRoomMessageEventArgs(string process, T data)
{
this.Process = process;
this.Message = data;
}
}
}
+120
View File
@@ -0,0 +1,120 @@
using System;
using System.IO;
using System.IO.Compression;
using System.Text.RegularExpressions;
using BarrageGrab.Modles.KsProtoEntity;
using BarrageGrab.Proxy.ProxyEventArgs;
using ProtoBuf;
namespace BarrageGrab
{
public class KsBarrageGrab
{
// 2026-05 快手WS域名变更为 livejs-ws.kuaishou.cn
private readonly Regex kuaishouWsReg = new Regex(@"livejs-ws\.kuaishou\.cn|(?:livemate|pclivemate)\.pull\.yximgs\.com", RegexOptions.IgnoreCase);
private const string KUAISHOU_LIVE_HOST = "live.kuaishou.com";
public event EventHandler<KsRoomMessageEventArgs<KsWebCommentFeed>> OnChatMessage;
public event EventHandler<KsRoomMessageEventArgs<KsWebLikeFeed>> OnLikeMessage;
public event EventHandler<KsRoomMessageEventArgs<KsWebGiftFeed>> OnGiftMessage;
public event EventHandler<KsRoomMessageEventArgs<KsWebShareFeed>> OnShareMessage;
public event EventHandler<KsRoomMessageEventArgs<KsSCWebFeedPush>> OnRoomStatsMessage;
public KsBarrageGrab()
{
}
public bool IsKuaishouHost(string hostname)
{
if (string.IsNullOrWhiteSpace(hostname)) return false;
hostname = hostname.Trim().ToLower();
return kuaishouWsReg.IsMatch(hostname) || hostname == KUAISHOU_LIVE_HOST;
}
public bool IsKuaishouWebSocket(string uri)
{
if (string.IsNullOrWhiteSpace(uri)) return false;
return kuaishouWsReg.IsMatch(uri);
}
public void HandleWebSocketData(WsMessageEventArgs e)
{
var buff = e.Payload;
if (buff == null || buff.Length == 0) return;
try
{
var socketMsg = Serializer.Deserialize<KsSocketMessage>(new ReadOnlyMemory<byte>(buff));
if (socketMsg == null) return;
if (socketMsg.PayloadType != KsPayloadType.SC_FEED_PUSH) return;
byte[] payload = socketMsg.Payload;
if (socketMsg.CompressionType == KsCompressionType.GZIP)
{
payload = Decompress(payload);
}
var feedPush = Serializer.Deserialize<KsSCWebFeedPush>(new ReadOnlyMemory<byte>(payload));
if (feedPush == null) return;
foreach (var comment in feedPush.CommentFeeds)
{
OnChatMessage?.Invoke(this, new KsRoomMessageEventArgs<KsWebCommentFeed>(e.ProcessName, comment));
}
foreach (var like in feedPush.LikeFeeds)
{
OnLikeMessage?.Invoke(this, new KsRoomMessageEventArgs<KsWebLikeFeed>(e.ProcessName, like));
}
foreach (var gift in feedPush.GiftFeeds)
{
OnGiftMessage?.Invoke(this, new KsRoomMessageEventArgs<KsWebGiftFeed>(e.ProcessName, gift));
}
foreach (var share in feedPush.ShareFeeds)
{
OnShareMessage?.Invoke(this, new KsRoomMessageEventArgs<KsWebShareFeed>(e.ProcessName, share));
}
OnRoomStatsMessage?.Invoke(this, new KsRoomMessageEventArgs<KsSCWebFeedPush>(e.ProcessName, feedPush));
}
catch (Exception ex)
{
Logger.LogError(ex, $"处理快手弹幕数据包时出错:{ex.Message}");
}
}
private byte[] Decompress(byte[] zippedData)
{
using (var ms = new MemoryStream(zippedData))
using (var gzip = new GZipStream(ms, CompressionMode.Decompress))
using (var outBuffer = new MemoryStream())
{
byte[] block = new byte[1024];
while (true)
{
int bytesRead = gzip.Read(block, 0, block.Length);
if (bytesRead <= 0) break;
outBuffer.Write(block, 0, bytesRead);
}
return outBuffer.ToArray();
}
}
}
public class KsRoomMessageEventArgs<T> : EventArgs where T : class
{
public string Process { get; set; }
public T Message { get; set; }
public KsRoomMessageEventArgs() { }
public KsRoomMessageEventArgs(string process, T data)
{
this.Process = process;
this.Message = data;
}
}
}
+354 -2
View File
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Drawing;
@@ -39,7 +39,9 @@ namespace BarrageGrab
ConcurrentDictionary<string, Tuple<int, DateTime>> giftCountCache = new ConcurrentDictionary<string, Tuple<int, DateTime>>();//礼物计数缓存
Timer dieout = new Timer(10000);//离线客户端清理计时器
Timer giftCountTimer = new Timer(10000);//礼物缓存清理计时器
WssBarrageGrab grab = new WssBarrageGrab();//弹幕解析器核心
WssBarrageGrab grab = new WssBarrageGrab();//抖音弹幕解析器核心
KsBarrageGrab ksGrab = new KsBarrageGrab();//快手弹幕解析器核心
BiliBarrageGrab biliGrab = new BiliBarrageGrab();//B站弹幕解析器核心
AppSetting Appsetting = AppSetting.Current;//全局配置文件实例
static int printCount = 0; //控制台输出计数,用于判断清理控制台
@@ -92,6 +94,43 @@ namespace BarrageGrab
this.grab.OnControlMessage += Grab_OnControlMessage;
this.grab.Proxy.OnLiveCompanAction += Proxy_OnLiveCompanAction;
//快手事件订阅
this.ksGrab.OnChatMessage += KsGrab_OnChatMessage;
this.ksGrab.OnLikeMessage += KsGrab_OnLikeMessage;
this.ksGrab.OnGiftMessage += KsGrab_OnGiftMessage;
this.ksGrab.OnShareMessage += KsGrab_OnShareMessage;
this.ksGrab.OnRoomStatsMessage += KsGrab_OnRoomStatsMessage;
//B站事件订阅
this.biliGrab.OnChatMessage += BiliGrab_OnChatMessage;
this.biliGrab.OnLikeMessage += BiliGrab_OnLikeMessage;
this.biliGrab.OnGiftMessage += BiliGrab_OnGiftMessage;
this.biliGrab.OnInteractMessage += BiliGrab_OnInteractMessage;
this.biliGrab.OnShareMessage += BiliGrab_OnShareMessage;
this.biliGrab.OnRoomStatsMessage += BiliGrab_OnRoomStatsMessage;
//多平台WS数据路由 - 将代理收到的WS数据按平台分发到对应解析器
this.grab.Proxy.OnWebSocketData += (s, e) =>
{
if (e.Platform == LivePlatform.Kuaishou)
{
this.ksGrab.HandleWebSocketData(e);
}
else if (e.Platform == LivePlatform.Bilibili)
{
this.biliGrab.HandleWebSocketData(e);
}
};
//B站发送数据路由 - 解析AUTH认证包提取roomid
this.grab.Proxy.OnWebSocketSentData += (s, e) =>
{
if (e.Platform == LivePlatform.Bilibili)
{
this.biliGrab.HandleSentData(e);
}
};
this.socketServer = socket;
//dieout.Start();
giftCountTimer.Start();
@@ -633,6 +672,319 @@ namespace BarrageGrab
}
}
#region 快手消息处理
private MsgUser KsUserToMsgUser(Modles.KsProtoEntity.KsSimpleUserInfo user)
{
if (user == null) return null;
return new MsgUser()
{
Id = long.TryParse(user.PrincipalId, out var pid) ? pid : 0,
DisplayId = user.PrincipalId ?? "",
Nickname = user.UserName ?? "用户" + user.PrincipalId,
HeadImgUrl = user.HeadUrl ?? "",
Gender = 0,
Level = 0,
PayLevel = 0,
ShortId = 0,
};
}
private string KsRoomId;
private void KsGrab_OnChatMessage(object sender, KsRoomMessageEventArgs<Modles.KsProtoEntity.KsWebCommentFeed> e)
{
var feed = e.Message;
var enty = new Msg()
{
MsgId = long.TryParse(feed.Id, out var id) ? id : DateTime.Now.Ticks,
User = KsUserToMsgUser(feed.User),
Content = feed.Content ?? "",
RoomId = KsRoomId ?? "",
WebRoomId = KsRoomId ?? "",
Appid = "0"
};
var msgType = PackMsgType.弹幕消息;
PrintMsg(enty, msgType);
var pack = new BarrageMsgPack(enty.ToJson(), msgType, e.Process, LivePlatform.Kuaishou);
Broadcast(pack);
}
private void KsGrab_OnLikeMessage(object sender, KsRoomMessageEventArgs<Modles.KsProtoEntity.KsWebLikeFeed> e)
{
var feed = e.Message;
var enty = new LikeMsg()
{
MsgId = long.TryParse(feed.Id, out var id) ? id : DateTime.Now.Ticks,
User = KsUserToMsgUser(feed.User),
Content = $"{feed.User?.UserName ?? "用户"} 为主播点了赞",
Count = 1,
Total = 0,
RoomId = KsRoomId ?? "",
WebRoomId = KsRoomId ?? "",
Appid = "0"
};
var msgType = PackMsgType.点赞消息;
PrintMsg(enty, msgType);
var pack = new BarrageMsgPack(enty.ToJson(), msgType, e.Process, LivePlatform.Kuaishou);
Broadcast(pack);
}
private void KsGrab_OnGiftMessage(object sender, KsRoomMessageEventArgs<Modles.KsProtoEntity.KsWebGiftFeed> e)
{
var feed = e.Message;
var enty = new GiftMsg()
{
MsgId = long.TryParse(feed.Id, out var id) ? id : DateTime.Now.Ticks,
User = KsUserToMsgUser(feed.User),
GiftId = feed.GiftId,
GiftName = KsGiftHelper.GetGiftName(feed.GiftId) ?? $"礼物#{feed.GiftId}",
GroupId = 0,
GiftCount = feed.ComboCount > 0 ? feed.ComboCount : 1,
RepeatCount = feed.ComboCount > 0 ? feed.ComboCount : 1,
DiamondCount = 0,
Combo = feed.ComboCount > 1,
Content = $"{feed.User?.UserName ?? "用户"} 送出 {KsGiftHelper.GetGiftName(feed.GiftId) ?? $"礼物#{feed.GiftId}"} x {feed.ComboCount}",
RoomId = KsRoomId ?? "",
WebRoomId = KsRoomId ?? "",
Appid = "0"
};
var msgType = PackMsgType.礼物消息;
PrintMsg(enty, msgType);
var pack = new BarrageMsgPack(enty.ToJson(), msgType, e.Process, LivePlatform.Kuaishou);
Broadcast(pack);
}
private void KsGrab_OnShareMessage(object sender, KsRoomMessageEventArgs<Modles.KsProtoEntity.KsWebShareFeed> e)
{
var feed = e.Message;
var enty = new ShareMessage()
{
MsgId = long.TryParse(feed.Id, out var id) ? id : DateTime.Now.Ticks,
User = KsUserToMsgUser(feed.User),
Content = $"{feed.User?.UserName ?? "用户"} 分享了直播间",
ShareType = ShareType.未知,
RoomId = KsRoomId ?? "",
WebRoomId = KsRoomId ?? "",
Appid = "0"
};
var msgType = PackMsgType.直播间分享;
PrintMsg(enty, msgType);
var pack = new BarrageMsgPack(enty.ToJson(), msgType, e.Process, LivePlatform.Kuaishou);
Broadcast(pack);
}
private void KsGrab_OnRoomStatsMessage(object sender, KsRoomMessageEventArgs<Modles.KsProtoEntity.KsSCWebFeedPush> e)
{
var feed = e.Message;
var enty = new UserSeqMsg()
{
MsgId = DateTime.Now.Ticks,
Content = $"当前直播间人数 {feed.DisplayWatchingCount},点赞 {feed.DisplayLikeCount}",
OnlineUserCount = long.TryParse(feed.DisplayWatchingCount, out var watching) ? watching : 0,
TotalUserCount = 0,
OnlineUserCountStr = feed.DisplayWatchingCount ?? "0",
TotalUserCountStr = "0",
RoomId = KsRoomId ?? "",
WebRoomId = KsRoomId ?? "",
Appid = "0"
};
var msgType = PackMsgType.直播间统计;
PrintMsg(enty, msgType);
var pack = new BarrageMsgPack(enty.ToJson(), msgType, e.Process, LivePlatform.Kuaishou);
Broadcast(pack);
}
#endregion
#region B站消息处理
private MsgUser BiliUserToMsgUser(Modles.BiliProtoEntity.BiliDanmuMsg user)
{
if (user == null) return null;
return new MsgUser()
{
Id = user.Uid,
DisplayId = user.Uid.ToString(),
Nickname = user.Uname ?? "用户" + user.Uid,
IsAdmin = user.IsAdmin,
IsAnchor = user.IsAnchor,
};
}
private MsgUser BiliUserToMsgUser(long uid, string uname)
{
return new MsgUser()
{
Id = uid,
DisplayId = uid.ToString(),
Nickname = string.IsNullOrWhiteSpace(uname) ? "用户" + uid : uname,
};
}
//B站弹幕
private void BiliGrab_OnChatMessage(object sender, BiliRoomMessageEventArgs<Modles.BiliProtoEntity.BiliDanmuMsg> e)
{
var msg = e.Message;
var enty = new Msg()
{
MsgId = DateTime.Now.Ticks,
User = BiliUserToMsgUser(msg),
Content = msg.Content ?? "",
RoomId = biliGrab.CurrentRoomId,
WebRoomId = biliGrab.CurrentRoomId,
Appid = "0"
};
var msgType = PackMsgType.弹幕消息;
PrintMsg(enty, msgType);
var pack = new BarrageMsgPack(enty.ToJson(), msgType, e.Process, LivePlatform.Bilibili);
Broadcast(pack);
}
//B站点赞
private void BiliGrab_OnLikeMessage(object sender, BiliRoomMessageEventArgs<Modles.BiliProtoEntity.BiliLikeData> e)
{
var msg = e.Message;
var enty = new LikeMsg()
{
MsgId = DateTime.Now.Ticks,
User = BiliUserToMsgUser(msg.Uid, msg.Uname),
Content = $"{msg.Uname ?? "用户"} 为主播点了赞",
Count = 1,
Total = 0,
RoomId = biliGrab.CurrentRoomId,
WebRoomId = biliGrab.CurrentRoomId,
Appid = "0"
};
var msgType = PackMsgType.点赞消息;
PrintMsg(enty, msgType);
var pack = new BarrageMsgPack(enty.ToJson(), msgType, e.Process, LivePlatform.Bilibili);
Broadcast(pack);
}
//B站礼物
private void BiliGrab_OnGiftMessage(object sender, BiliRoomMessageEventArgs<Modles.BiliProtoEntity.BiliGiftData> e)
{
var msg = e.Message;
var count = msg.Num > 0 ? msg.Num : 1;
var enty = new GiftMsg()
{
MsgId = DateTime.Now.Ticks,
User = BiliUserToMsgUser(msg.Uid, msg.Uname),
GiftId = msg.GiftId,
GiftName = msg.GiftName ?? $"礼物#{msg.GiftId}",
GroupId = 0,
GiftCount = count,
RepeatCount = msg.SuperBatchNum > 0 ? msg.SuperBatchNum : count,
DiamondCount = (int)(msg.Price * 1000),
Combo = msg.ComboNum > 1,
ImgUrl = msg.GifUrl ?? "",
Content = $"{msg.Uname ?? "用户"} 送出 {msg.GiftName ?? $"礼物#{msg.GiftId}"} x {count}",
RoomId = biliGrab.CurrentRoomId,
WebRoomId = biliGrab.CurrentRoomId,
Appid = "0"
};
var msgType = PackMsgType.礼物消息;
PrintMsg(enty, msgType);
var pack = new BarrageMsgPack(enty.ToJson(), msgType, e.Process, LivePlatform.Bilibili);
Broadcast(pack);
}
//B站进房/关注
private void BiliGrab_OnInteractMessage(object sender, BiliRoomMessageEventArgs<Modles.BiliProtoEntity.BiliInteractData> e)
{
var msg = e.Message;
// msg_type: 1=进入 2=关注
if (msg.MsgType == 2)
{
var enty = new Msg()
{
MsgId = DateTime.Now.Ticks,
User = BiliUserToMsgUser(msg.Uid, msg.Uname),
Content = $"{msg.Uname ?? "用户"} 关注了主播",
RoomId = biliGrab.CurrentRoomId,
WebRoomId = biliGrab.CurrentRoomId,
Appid = "0"
};
var msgType = PackMsgType.关注消息;
PrintMsg(enty, msgType);
var pack = new BarrageMsgPack(enty.ToJson(), msgType, e.Process, LivePlatform.Bilibili);
Broadcast(pack);
}
else
{
var enty = new Modles.JsonEntity.MemberMessage()
{
MsgId = DateTime.Now.Ticks,
User = BiliUserToMsgUser(msg.Uid, msg.Uname),
Content = $"{msg.Uname ?? "用户"} 来了直播间",
CurrentCount = 0,
EnterTipType = 0,
RoomId = biliGrab.CurrentRoomId,
WebRoomId = biliGrab.CurrentRoomId,
Appid = "0"
};
var msgType = PackMsgType.进直播间;
PrintMsg(enty, msgType);
var pack = new BarrageMsgPack(enty.ToJson(), msgType, e.Process, LivePlatform.Bilibili);
Broadcast(pack);
}
}
//B站分享
private void BiliGrab_OnShareMessage(object sender, BiliRoomMessageEventArgs<Modles.BiliProtoEntity.BiliInteractData> e)
{
var msg = e.Message;
var enty = new ShareMessage()
{
MsgId = DateTime.Now.Ticks,
User = BiliUserToMsgUser(msg.Uid, msg.Uname),
Content = $"{msg.Uname ?? "用户"} 分享了直播间",
ShareType = ShareType.未知,
RoomId = biliGrab.CurrentRoomId,
WebRoomId = biliGrab.CurrentRoomId,
Appid = "0"
};
var msgType = PackMsgType.直播间分享;
PrintMsg(enty, msgType);
var pack = new BarrageMsgPack(enty.ToJson(), msgType, e.Process, LivePlatform.Bilibili);
Broadcast(pack);
}
//B站房间统计
private void BiliGrab_OnRoomStatsMessage(object sender, BiliRoomMessageEventArgs<Modles.BiliProtoEntity.BiliRoomStats> e)
{
var msg = e.Message;
var enty = new UserSeqMsg()
{
MsgId = DateTime.Now.Ticks,
Content = $"当前直播间人数 {msg.Online},看过 {msg.Watched}",
OnlineUserCount = msg.Online,
TotalUserCount = msg.Watched,
OnlineUserCountStr = msg.Online.ToString(),
TotalUserCountStr = msg.Watched.ToString(),
RoomId = biliGrab.CurrentRoomId,
WebRoomId = biliGrab.CurrentRoomId,
Appid = "0"
};
var msgType = PackMsgType.直播间统计;
PrintMsg(enty, msgType);
var pack = new BarrageMsgPack(enty.ToJson(), msgType, e.Process, LivePlatform.Bilibili);
Broadcast(pack);
}
#endregion
//监听用户连接
private void Listen(IWebSocketConnection socket)
{
+17 -4
View File
@@ -5,6 +5,7 @@ using System.IO.Compression;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using BarrageGrab.Modles.JsonEntity;
using BarrageGrab.Modles.ProtoEntity;
using BarrageGrab.Proxy;
using BarrageGrab.Proxy.ProxyEventArgs;
@@ -24,7 +25,7 @@ namespace BarrageGrab
/// <summary>
/// 进入直播间
/// </summary>
public event EventHandler<RoomMessageEventArgs<MemberMessage>> OnMemberMessage;
public event EventHandler<RoomMessageEventArgs<Modles.ProtoEntity.MemberMessage>> OnMemberMessage;
/// <summary>
/// 关注
@@ -103,8 +104,10 @@ namespace BarrageGrab
}
//ws数据处理
private static int douyinErrCount = 0;
private void Proxy_OnWebSocketData(object sender, WsMessageEventArgs e)
{
if (e.Platform != LivePlatform.Douyin) return;
if (!appsetting.ProcessFilter.Contains(e.ProcessName)) return;
var buff = e.Payload;
if (buff.Length == 0) return;
@@ -129,7 +132,17 @@ namespace BarrageGrab
}
catch (Exception ex)
{
Logger.LogError(ex, $"处理弹幕数据包时出错:{ex.Message}");
// 限流:同类错误最多打印3次,防止刷屏和连锁崩溃
if (douyinErrCount < 3)
{
Logger.LogError(ex, $"处理抖音弹幕数据包时出错:{ex.Message}");
douyinErrCount++;
}
if (douyinErrCount == 3)
{
Logger.LogWarn("抖音弹幕解析持续失败,已抑制后续错误日志(不影响快手弹幕)");
douyinErrCount++;
}
}
}
@@ -183,8 +196,8 @@ namespace BarrageGrab
//来了
case "WebcastMemberMessage":
{
var arg = Serializer.Deserialize<MemberMessage>(new ReadOnlyMemory<byte>(msg.Payload));
this.OnMemberMessage?.Invoke(this, new RoomMessageEventArgs<MemberMessage>(processName, arg));
var arg = Serializer.Deserialize<Modles.ProtoEntity.MemberMessage>(new ReadOnlyMemory<byte>(msg.Payload));
this.OnMemberMessage?.Invoke(this, new RoomMessageEventArgs<Modles.ProtoEntity.MemberMessage>(processName, arg));
break;
}
//关注