diff --git a/BarrageGrab/App.config b/BarrageGrab/App.config index ecfd6cd..db48bf7 100644 --- a/BarrageGrab/App.config +++ b/BarrageGrab/App.config @@ -7,9 +7,11 @@ - + + + diff --git a/BarrageGrab/AppRuntime.cs b/BarrageGrab/AppRuntime.cs index 35b792c..1af34e8 100644 --- a/BarrageGrab/AppRuntime.cs +++ b/BarrageGrab/AppRuntime.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Linq; @@ -20,6 +21,11 @@ namespace BarrageGrab /// public static WsBarrageService WssService { get; private set; } = new WsBarrageService(); + /// + /// 房间缓存信息 + /// + public static RoomCacheManager RoomCaches { get; } = new RoomCacheManager(); + /// /// 程序进程信息 /// @@ -29,6 +35,81 @@ namespace BarrageGrab static AppRuntime() { - } + } + + /// + /// 房间缓存管理器 + /// + public class RoomCacheManager + { + /// + /// 房间ID - 房间信息映射缓存 + /// + public ConcurrentDictionary RoomInfoCache { get; } = new ConcurrentDictionary(); + + /// + /// 添加WebRoomid-房间ID 映射 + /// + /// + /// + public void SetRoomCache(string roomid, string webrid) + { + if (RoomInfoCache.ContainsKey(roomid)) + { + RoomInfoCache[roomid].WebRoomId = webrid; + } + else + { + RoomInfoCache.TryAdd(roomid, new RoomInfo() + { + RoomId = roomid, + WebRoomId = webrid, + Title = "房间" + webrid + }); + } + } + + /// + /// 添加房间缓存信息 + /// + /// + /// + public void AddRoomInfoCache(RoomInfo roomInfo) + { + if (roomInfo == null) return; + var roomid = roomInfo.RoomId; + if (RoomInfoCache.ContainsKey(roomid)) + { + RoomInfoCache[roomid] = roomInfo; + } + else + { + RoomInfoCache.TryAdd(roomid, roomInfo); + } + } + + /// + /// 根据roomid从缓存获取webRoomid + /// + /// + /// + public long GetCachedWebRoomid(string roomid) + { + if (RoomInfoCache.TryGetValue(roomid, out var value)) return long.Parse(value.WebRoomId); + return -1; + } + + /// + /// 根据roomid从缓存获取房间信息 + /// + /// + /// + public RoomInfo GetCachedWebRoomInfo(string roomid) + { + if (RoomInfoCache.TryGetValue(roomid, out var value)) return value; + return null; + } + } } + } diff --git a/BarrageGrab/Appsetting.cs b/BarrageGrab/Appsetting.cs index bf7d8c1..ee0297f 100644 --- a/BarrageGrab/Appsetting.cs +++ b/BarrageGrab/Appsetting.cs @@ -22,12 +22,13 @@ namespace BarrageGrab WsProt = int.Parse(AppSettings["wsListenPort"]); PrintBarrage = AppSettings["printBarrage"].ToLower() == "true"; ProxyPort = int.Parse(AppSettings["proxyPort"]); - PrintFilter = Enum.GetValues(typeof(BarrageMsgType)).Cast().ToArray(); + PrintFilter = Enum.GetValues(typeof(PackMsgType)).Cast().ToArray(); FilterHostName = bool.Parse(AppSettings["filterHostName"].Trim()); HostNameFilter = AppSettings["hostNameFilter"].Trim().Split(',').Where(w => !string.IsNullOrWhiteSpace(w)).ToArray(); RoomIds = AppSettings["roomIds"].Trim().Split(',').Where(w => !string.IsNullOrWhiteSpace(w)).Select(s => long.Parse(s)).ToArray(); UsedProxy = bool.Parse(AppSettings["usedProxy"].Trim()); ListenAny = bool.Parse(AppSettings["listenAny"].Trim()); + UpstreamProxy = AppSettings["upstreamProxy"].Trim(); var printFilter = AppSettings["printFilter"].Trim().ToLower(); if (printFilter != "all") @@ -92,5 +93,10 @@ namespace BarrageGrab /// 域名白名单列表 /// public string[] HostNameFilter { get; private set; } = new string[0]; + + /// + /// 上游代理地址 + /// + public string UpstreamProxy { get; set; } } } diff --git a/BarrageGrab/Modles/JsonEntity/BarrageMessages.cs b/BarrageGrab/Modles/JsonEntity/BarrageMessages.cs index 9960ec7..b9b5741 100644 --- a/BarrageGrab/Modles/JsonEntity/BarrageMessages.cs +++ b/BarrageGrab/Modles/JsonEntity/BarrageMessages.cs @@ -3,7 +3,7 @@ /// /// 弹幕消息类型 /// - public enum BarrageMsgType + public enum PackMsgType { 无 = 0, 弹幕消息 = 1, @@ -50,7 +50,12 @@ /// /// 消息类型 /// - public BarrageMsgType Type { get; set; } + public PackMsgType Type { get; set; } + + /// + /// 进程名 + /// + public string ProcessName { get; set; } /// /// 消息对象 @@ -62,10 +67,11 @@ } - public BarrageMsgPack(string data, BarrageMsgType type) + public BarrageMsgPack(string data, PackMsgType type,string processName) { Data = data; Type = type; + ProcessName = processName; } } @@ -93,6 +99,11 @@ /// 房间号 /// public long RoomId { get; set; } + + /// + /// web直播间ID + /// + public long WebRoomId { get; set; } } /// diff --git a/BarrageGrab/Modles/RoomInfo.cs b/BarrageGrab/Modles/RoomInfo.cs new file mode 100644 index 0000000..95f9ae7 --- /dev/null +++ b/BarrageGrab/Modles/RoomInfo.cs @@ -0,0 +1,576 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using Newtonsoft.Json.Linq; +using Newtonsoft.Json; + +namespace BarrageGrab.Modles +{ + [Serializable] + public class RoomInfo + { + /// + /// Web房间号 + /// + public string WebRoomId { get; set; } + + /// + /// 房间号 + /// + public string RoomId { get; set; } + + /// + /// 直播间名称 + /// + public string Title { get; set; } + + /// + /// 是否直播中 + /// + public bool IsLive { get; set; } + + /// + /// 实时在线人数 + /// + public long UserCount { get; set; } + + /// + /// 累计在线人数 + /// + public string TotalUserCount { get; set; } + + /// + /// 点赞数 + /// + public long LikeCount { get; set; } + + /// + /// 直播间二维码图像链接 + /// + public string QrcodeUrl { get; set; } + + /// + /// 直播间地址 + /// + public string LiveUrl { get; set; } + + /// + /// 封面链接 + /// + public string Cover { get; set; } + + /// + /// 直播间管理员用户id + /// + public List AdminUserIds { get; set; } = new List(); + + /// + /// 主播信息 + /// + public RoomAnchor Owner { get; set; } + + /// + /// 直播间的一些配置 + /// + public RoomAuth AuthInfo { get; set; } + + /// + /// 自身信息,Api方式没有 + /// + public OdinData Odin { get; set; } + + /// + /// Response请求头设置的 Ttwid,Api方式没有 + /// + public string Ttwid { get; set; } + + public class RoomAnchor + { + /// + /// 用户ID + /// + public string UserId { get; set; } + + /// + /// SecUid + /// + public string SecUid { get; set; } + + /// + /// 昵称 + /// + public string Nickname { get; set; } + + /// + /// 头像地址 + /// + public string HeadUrl { get; set; } + + /// + /// 关注状态 0未关注,1已关注,... + /// + public int FollowStatus { get; set; } + } + + public class RoomAuth + { + /// + /// 是否允许聊天 + /// + public bool Chat { get; set; } + + /// + /// 是否允许弹幕 + /// + public bool Danmaku { get; set; } + + /// + /// 是否允许送礼物 + /// + public bool Gift { get; set; } + + /// + /// 是否允许发红包 + /// + public bool LuckMoney { get; set; } + + /// + /// 是否允许点赞 + /// + public bool Digg { get; set; } + + /// + /// 是否允许房间贡献者 + /// + public bool RoomContributor { get; set; } + + /// + /// 是否允许使用道具 + /// + public bool Props { get; set; } + + /// + /// 是否允许查看用户名片 + /// + public bool UserCard { get; set; } + + /// + /// 是否允许查看 POI + /// + public bool POI { get; set; } + + /// + /// 更多主播? + /// + public int MoreAnchor { get; set; } + + /// + /// banner? + /// + public int Banner { get; set; } + + /// + /// 分享配置? + /// + public int Share { get; set; } + + /// + /// 用户角标? + /// + public int UserCorner { get; set; } + + /// + /// 横屏? + /// + public int Landscape { get; set; } + + /// + /// 横屏聊天? + /// + public int LandscapeChat { get; set; } + + /// + /// 公屏? + /// + public int PublicScreen { get; set; } + + /// + /// 礼物主播魔法表情配置? + /// + public int GiftAnchorMt { get; set; } + + /// + /// 录屏配置? + /// + public int RecordScreen { get; set; } + + /// + /// 打赏贴纸配置? + /// + public int DonationSticker { get; set; } + + /// + /// 小时榜配置? + /// + public int HourRank { get; set; } + + /// + /// 商务名片配置? + /// + public int CommerceCard { get; set; } + + /// + /// 语音聊天配置? + /// + public int AudioChat { get; set; } + + /// + /// 弹幕默认开关 + /// + public int DanmakuDefault { get; set; } + + /// + /// KTV 点歌? + /// + public int KtvOrderSong { get; set; } + + /// + /// 选集? + /// + public int SelectionAlbum { get; set; } + + /// + /// 点赞? + /// + public int Like { get; set; } + + /// + /// 倍速播放? + /// + public int MultiplierPlayback { get; set; } + + /// + /// 下载视频? + /// + public int DownloadVideo { get; set; } + + /// + /// 收藏? + /// + public int Collect { get; set; } + + /// + /// 定时下播? + /// + public int TimedShutdown { get; set; } + + /// + /// 快进? + /// + public int Seek { get; set; } + + /// + /// 举报? + /// + public int Denounce { get; set; } + + /// + /// 踩? + /// + public int Dislike { get; set; } + + /// + /// 只看 TA? + /// + public int OnlyTa { get; set; } + + /// + /// 投屏? + /// + public int CastScreen { get; set; } + + /// + /// 评论墙? + /// + public int CommentWall { get; set; } + + /// + /// 弹幕样式 + /// + public object BulletStyle { get; set; } + + /// + /// 显示游戏插件? + /// + public int ShowGamePlugin { get; set; } + + /// + /// PK礼物? + /// + public int VSGift { get; set; } + + /// + /// PK话题? + /// + public int VSTopic { get; set; } + + /// + /// PK排行榜? + /// + public int VSRank { get; set; } + + /// + /// 管理员评论墙? + /// + public int AdminCommentWall { get; set; } + + /// + /// 商务组件? + /// + public int CommerceComponent { get; set; } + + /// + /// 抖音会员? + /// + public int DouPlus { get; set; } + + /// + /// 游戏积分玩法? + /// + public int GamePointsPlaying { get; set; } + + /// + /// 海报? + /// + public int Poster { get; set; } + + /// + /// 精彩时刻? + /// + public int Highlights { get; set; } + + /// + /// 打字评论状态? + /// + public int TypingCommentState { get; set; } + + /// + /// 上下滑动引导? + /// + public int StrokeUpDownGuide { get; set; } + + /// + /// 右上角统计浮层? + /// + public int UpRightStatsFloatingLayer { get; set; } + + /// + /// 投屏显式? + /// + public int CastScreenExplicit { get; set; } + + /// + /// 选集? + /// + public int Selection { get; set; } + + /// + /// 行业服务? + /// + public int IndustryService { get; set; } + + /// + /// 竖屏排行榜? + /// + public int VerticalRank { get; set; } + + /// + /// 进场特效? + /// + public int EnterEffects { get; set; } + + /// + /// 粉丝团? + /// + public int FansClub { get; set; } + + /// + /// 表情外显? + /// + public int EmojiOutside { get; set; } + + /// + /// 售票? + /// + public int CanSellTicket { get; set; } + + /// + /// 抖音会员人气宝石? + /// + public int DouPlusPopularityGem { get; set; } + + /// + /// 任务中心? + /// + public int MissionCenter { get; set; } + + /// + ///扩展屏幕? + /// + public int ExpandScreen { get; set; } + + /// + /// 粉丝群? + /// + public int FansGroup { get; set; } + + /// + /// 话题? + /// + public int Topic { get; set; } + + /// + /// 主播任务? + /// + public int AnchorMission { get; set; } + + /// + /// 跑马灯? + /// + public int Teleprompter { get; set; } + + /// + /// 长按? + /// + public int LongTouch { get; set; } + + /// + /// 特殊样式 + /// + public SpecialStyle SpecialStyle { get; set; } + + /// + /// 固定聊天? + /// + public int FixedChat { get; set; } + + /// + /// 竞猜? + /// + public int QuizGamePointsPlaying { get; set; } + } + + public class ChatStyle + { + /// + /// 是否禁用样式 + /// + public int UnableStyle { get; set; } + + /// + /// 内容 + /// + public string Content { get; set; } + + /// + /// 关闭类型 + /// + public int OffType { get; set; } + } + + public class SpecialStyle + { + /// + /// 聊天样式 + /// + public ChatStyle Chat { get; set; } + + /// + /// 点赞样式 + /// + public ChatStyle Like { get; set; } + } + + public class OdinData + { + public string user_id { get; set; } + public string user_type { get; set; } + public string user_is_auth { get; set; } + public string user_is_login { get; set; } + public string user_unique_id { get; set; } + } + + + /// + /// 将 直播间页面元素 转为房间信息对象 + /// + /// + /// + /// + public static (int, string) TryParseRoomPageHtml(string html, out RoomInfo result) + { + result = null; + if (html.IsNullOrWhiteSpace()) + { + return (-1, "无效的页面数据"); + } + var roomDataReg = new Regex(@"(?<=self\.__pace_f\.push\(\[1,""a:)\[.+?\](?=\\n?""\]\)[\s\n\r]*?<\/script>)"); + var match = roomDataReg.Match(html); + if (!match.Success) + { + Logger.LogError("在通过正则匹配直播页房间信息时失败,可能是官方做了升级"); + return (1, "未能匹配到房间信息"); + } + var matchData = Regex.Unescape(match.Value); + + JToken jsonObject = null; + try + { + jsonObject = JsonConvert.DeserializeObject(matchData)[3]; + } + catch (Exception ex) + { + return (2, "匹配到的房间数据格式有错误"); + } + + var roomInfo = jsonObject["state"]["roomStore"]["roomInfo"]; + var odin = jsonObject["state"]["userStore"]["odin"]; + var room = roomInfo["room"]; + var roomOwner = roomInfo["anchor"]; + + if (room == null) + { + return (3, "房间信息为空,获取失败"); + } + + result = new RoomInfo(); + result.Odin = odin?.ToObject(); + result.WebRoomId = roomInfo["web_rid"]?.ToString() ?? ""; + result.RoomId = roomInfo["roomId"]?.ToString() ?? ""; + result.AdminUserIds = room["admin_user_ids_str"]?.Values()?.ToList() ?? new List(); + result.IsLive = room["status"]?.Value() == 2; + result.Title = room["title"]?.ToString() ?? ""; + result.UserCount = room["room_view_stats"]?["display_value"]?.Value() ?? 0; + result.TotalUserCount = room["stats"]?["total_user_str"]?.Value() ?? "0"; + result.LikeCount = room["like_count"]?.Value() ?? 0; + result.QrcodeUrl = roomInfo["qrcode_url"]?.ToString() ?? ""; + result.AuthInfo = room["room_auth"]?.ToObject(); + result.Cover = room["cover"]?["url_list"]?.Values().FirstOrDefault() ?? "";//下播情况下没有cover字段 + if (roomOwner != null) + { + result.Owner = new RoomInfo.RoomAnchor() + { + UserId = roomOwner["id_str"].ToString(), + Nickname = roomOwner["nickname"].ToString(), + SecUid = roomOwner["sec_uid"].ToString(), + HeadUrl = roomOwner["avatar_thumb"]["url_list"].Values()?.FirstOrDefault() ?? "", + FollowStatus = roomOwner["follow_info"]["follow_status"].Value() + }; + } + return (0, "succ"); + } + } +} diff --git a/BarrageGrab/Proxy/TitaniumProxy.cs b/BarrageGrab/Proxy/TitaniumProxy.cs index 7231e90..6fe956c 100644 --- a/BarrageGrab/Proxy/TitaniumProxy.cs +++ b/BarrageGrab/Proxy/TitaniumProxy.cs @@ -21,6 +21,7 @@ namespace BarrageGrab.Proxy { ProxyServer proxyServer = null; ExplicitProxyEndPoint explicitEndPoint = null; + ExternalProxy upStreamProxy = null; const string SCRIPT_HOST = "lf-cdn-tos.bytescm.com"; const string LIVE_HOST = "live.douyin.com"; @@ -29,7 +30,6 @@ namespace BarrageGrab.Proxy const string BARRAGE_POOL_PATH = "/webcast/im/fetch"; static ConsoleWriter console = new ConsoleWriter(); - public TitaniumProxy() { //注册系统代理 @@ -48,6 +48,20 @@ namespace BarrageGrab.Proxy proxyServer.CertificateManager.CreateRootCertificate(); } + //设置上游代理地址 + var upstreamProxyAddr = Appsetting.Current.UpstreamProxy; + if (!upstreamProxyAddr.IsNullOrWhiteSpace()) + { + upStreamProxy = new ExternalProxy() + { + HostName = upstreamProxyAddr.Split(':')[0], + Port = int.Parse(upstreamProxyAddr.Split(':')[1]), + ProxyType = ExternalProxyType.Http + }; + proxyServer.UpStreamHttpProxy = upStreamProxy; + proxyServer.UpStreamHttpsProxy = upStreamProxy; + } + proxyServer.ServerCertificateValidationCallback += ProxyServer_ServerCertificateValidationCallback; proxyServer.BeforeResponse += ProxyServer_BeforeResponse; //proxyServer.AfterResponse += ProxyServer_AfterResponse; @@ -58,7 +72,6 @@ namespace BarrageGrab.Proxy proxyServer.AddEndPoint(explicitEndPoint); } - private async Task ProxyServer_BeforeResponse(object sender, SessionEventArgs e) { string uri = e.HttpClient.Request.RequestUri.ToString(); @@ -131,7 +144,7 @@ namespace BarrageGrab.Proxy var urlNoQuery = url.Split('?')[0]; var processid = e.HttpClient.ProcessId.Value; var processName = base.GetProcessName(processid); - var isLiveRoom = Regex.IsMatch(uri.Trim(), @".*:\/\/live.douyin\.com\/\d+"); + var liveRoomMactch = Regex.Match(uri.Trim(), @".*:\/\/live.douyin\.com\/(\d+)"); var contentType = e.HttpClient.Response.ContentType ?? ""; //检测是否为dom页,用于脚本注入 @@ -145,8 +158,9 @@ namespace BarrageGrab.Proxy } //获取 content-type - if (isLiveRoom) + if (liveRoomMactch.Success) { + string webrid = liveRoomMactch.Groups[1].Value; //获取直播页注入js string liveRoomInjectScript = GetInjectScript("livePage"); @@ -158,6 +172,16 @@ namespace BarrageGrab.Proxy { //利用 HtmlAgilityPack 在尾部注入script 标签 var html = await e.GetResponseBodyAsString(); + + (int code, string msg) = RoomInfo.TryParseRoomPageHtml(html, out var roominfo); + if (code == 0) + { + Logger.LogInfo($"直播页{webrid}房间信息已采集到缓存"); + roominfo.WebRoomId = webrid; + roominfo.LiveUrl = url; + AppRuntime.RoomCaches.AddRoomInfoCache(roominfo); + } + try { var doc = new HtmlAgilityPack.HtmlDocument(); @@ -218,7 +242,6 @@ namespace BarrageGrab.Proxy } } } - private Task ProxyServer_ServerCertificateValidationCallback(object sender, CertificateValidationEventArgs e) { diff --git a/BarrageGrab/WsBarrageService.cs b/BarrageGrab/WsBarrageService.cs index 5169eb8..f0c1de9 100644 --- a/BarrageGrab/WsBarrageService.cs +++ b/BarrageGrab/WsBarrageService.cs @@ -122,58 +122,104 @@ namespace BarrageGrab return user; } - //粉丝团 - private void Grab_OnFansclubMessage(object sender, FansclubMessage e) + static int count = 0; + private void PrintMsg(Msg msg, PackMsgType barType) { - if (!CheckRoomId(e.Common.roomId)) return; + if (!Appsetting.PrintBarrage) return; + + var rinfo = AppRuntime.RoomCaches.GetCachedWebRoomInfo(msg.RoomId.ToString()); + var roomName = (rinfo?.Owner?.Nickname ?? ("直播间" + (msg.WebRoomId == 0 ? msg.RoomId : msg.WebRoomId))); + var text = $"{DateTime.Now.ToString("HH:mm:ss")} [{roomName}] [{barType}]"; + + if (msg.User != null) + { + text += $" [{msg.User?.GenderToString()}] "; + } + + ConsoleColor color = ConsoleColor.White; + var append = msg.Content; + switch (barType) + { + case PackMsgType.弹幕消息: color = ConsoleColor.White; append = $"{msg?.User?.Nickname}: {msg.Content}"; break; + case PackMsgType.点赞消息: color = ConsoleColor.Cyan; break; + case PackMsgType.进直播间: color = ConsoleColor.Green; break; + case PackMsgType.关注消息: color = ConsoleColor.Yellow; break; + case PackMsgType.礼物消息: color = ConsoleColor.Red; break; + case PackMsgType.直播间统计: color = ConsoleColor.Magenta; break; + case PackMsgType.粉丝团消息: color = ConsoleColor.Blue; break; + case PackMsgType.直播间分享: color = ConsoleColor.DarkBlue; break; + case PackMsgType.下播: color = ConsoleColor.DarkCyan; append = $"直播已结束"; break; + default: break; + } + text += append; + + if (++count > 10000) + { + Console.Clear(); + Console.WriteLine("控制台已清理"); + count = 0; + } + console.WriteLine(text + "\n", color); + } + + //粉丝团 + private void Grab_OnFansclubMessage(object sender, WssBarrageGrab.RoomMessageEventArgs e) + { + var msg = e.Message; + if (!CheckRoomId(msg.Common.roomId)) return; var enty = new FansclubMsg() { - MsgId = e.Common.msgId, - Content = e.Content, - RoomId = e.Common.roomId, - Type = e.Type, - User = GetUser(e.User) + MsgId = msg.Common.msgId, + Content = msg.Content, + RoomId = msg.Common.roomId, + WebRoomId = AppRuntime.RoomCaches.GetCachedWebRoomid(msg.Common.roomId.ToString()), + Type = msg.Type, + User = GetUser(msg.User) }; enty.Level = enty.User.FansClub.Level; - Print(enty.User.GenderToString() + " " + enty.Content, ConsoleColor.Blue, BarrageMsgType.粉丝团消息); - var pack = new BarrageMsgPack(JsonConvert.SerializeObject(enty), BarrageMsgType.粉丝团消息); - var json = JsonConvert.SerializeObject(pack); - this.Broadcast(json); + + var msgType = PackMsgType.粉丝团消息; + PrintMsg(enty, msgType); + Broadcast(new BarrageMsgPack(enty.ToJson(), msgType,e.Process)); } //统计消息 - private void Grab_OnRoomUserSeqMessage(object sender, RoomUserSeqMessage e) + private void Grab_OnRoomUserSeqMessage(object sender, WssBarrageGrab.RoomMessageEventArgs e) { - if (!CheckRoomId(e.Common.roomId)) return; + var msg = e.Message; + if (!CheckRoomId(msg.Common.roomId)) return; var enty = new UserSeqMsg() { - MsgId = e.Common.msgId, - OnlineUserCount = e.Total, - TotalUserCount = e.totalUser, - TotalUserCountStr = e.totalPvForAnchor, - OnlineUserCountStr = e.onlineUserForAnchor, - RoomId = e.Common.roomId, - Content = $"当前直播间人数 {e.onlineUserForAnchor},累计直播间人数 {e.totalPvForAnchor}", + MsgId = msg.Common.msgId, + OnlineUserCount = msg.Total, + TotalUserCount = msg.totalUser, + TotalUserCountStr = msg.totalPvForAnchor, + OnlineUserCountStr = msg.onlineUserForAnchor, + RoomId = msg.Common.roomId, + WebRoomId = AppRuntime.RoomCaches.GetCachedWebRoomid(msg.Common.roomId.ToString()), + Content = $"当前直播间人数 {msg.onlineUserForAnchor},累计直播间人数 {msg.totalPvForAnchor}", User = null }; - Print(enty.Content, ConsoleColor.Magenta, BarrageMsgType.直播间统计); - var pack = new BarrageMsgPack(JsonConvert.SerializeObject(enty), BarrageMsgType.直播间统计); - var json = JsonConvert.SerializeObject(pack); - this.Broadcast(json); + + var msgType = PackMsgType.直播间统计; + PrintMsg(enty, msgType); + var pack = new BarrageMsgPack(enty.ToJson(), msgType, e.Process); + Broadcast(pack); } //礼物 - private void Grab_OnGiftMessage(object sender, GiftMessage e) + private void Grab_OnGiftMessage(object sender, WssBarrageGrab.RoomMessageEventArgs e) { - if (!CheckRoomId(e.Common.roomId)) return; + var msg = e.Message; + if (!CheckRoomId(msg.Common.roomId)) return; - var key = e.giftId + "-" + e.groupId.ToString(); + var key = msg.Common.roomId + "-" + msg.giftId + "-" + msg.groupId.ToString(); //判断礼物重复 - if (e.repeatEnd == 1) + if (msg.repeatEnd == 1) { //清除缓存中的key - if (e.groupId > 0 && giftCountCache.ContainsKey(key)) + if (msg.groupId > 0 && giftCountCache.ContainsKey(key)) { lock (giftCountCache) { @@ -184,7 +230,7 @@ namespace BarrageGrab } int lastCount = 0; - int currCount = (int)e.repeatCount; + int currCount = (int)msg.repeatCount; var backward = currCount <= lastCount; if (currCount <= 0) currCount = 1; @@ -202,7 +248,7 @@ namespace BarrageGrab } else { - if (e.groupId > 0 && !backward) + if (msg.groupId > 0 && !backward) { lock (giftCountCache) { @@ -218,175 +264,190 @@ namespace BarrageGrab var enty = new GiftMsg() { - MsgId = e.Common.msgId, - RoomId = e.Common.roomId, - Content = $"{e.User.Nickname} 送出 {e.Gift.Name} x {currCount} 个,增量{count}个", - DiamondCount = e.Gift.diamondCount, + MsgId = msg.Common.msgId, + RoomId = msg.Common.roomId, + WebRoomId = AppRuntime.RoomCaches.GetCachedWebRoomid(msg.Common.roomId.ToString()), + Content = $"{msg.User.Nickname} 送出 {msg.Gift.Name} x {currCount} 个,增量{count}个", + DiamondCount = msg.Gift.diamondCount, RepeatCount = currCount, GiftCount = count, - GroupId = e.groupId, - GiftId = e.giftId, - GiftName = e.Gift.Name, - User = GetUser(e.User) + GroupId = msg.groupId, + GiftId = msg.giftId, + GiftName = msg.Gift.Name, + User = GetUser(msg.User) }; - - - Print($"{enty.User.GenderToString()} {enty.Content}", ConsoleColor.Red, BarrageMsgType.礼物消息); - var pack = new BarrageMsgPack(JsonConvert.SerializeObject(enty), BarrageMsgType.礼物消息); - var json = JsonConvert.SerializeObject(pack); - this.Broadcast(json); + + var msgType = PackMsgType.礼物消息; + PrintMsg(enty, msgType); + var pack = new BarrageMsgPack(enty.ToJson(), PackMsgType.礼物消息, e.Process); + Broadcast(pack); } //关注 - private void Grab_OnSocialMessage(object sender, SocialMessage e) + private void Grab_OnSocialMessage(object sender, WssBarrageGrab.RoomMessageEventArgs e) { - if (!CheckRoomId(e.Common.roomId)) return; - if (e.Action != 1) return; + var msg = e.Message; + if (!CheckRoomId(msg.Common.roomId)) return; + if (msg.Action != 1) return; var enty = new Msg() { - MsgId = e.Common.msgId, - Content = $"{e.User.Nickname} 关注了主播", - RoomId = e.Common.roomId, - User = GetUser(e.User) + MsgId = msg.Common.msgId, + Content = $"{msg.User.Nickname} 关注了主播", + RoomId = msg.Common.roomId, + WebRoomId = AppRuntime.RoomCaches.GetCachedWebRoomid(msg.Common.roomId.ToString()), + User = GetUser(msg.User) }; - - Print($"{enty.User.GenderToString()} {enty.Content}", ConsoleColor.Yellow, BarrageMsgType.关注消息); - var pack = new BarrageMsgPack(JsonConvert.SerializeObject(enty), BarrageMsgType.关注消息); + + var msgType = PackMsgType.关注消息; + PrintMsg(enty, msgType); + var pack = new BarrageMsgPack(enty.ToJson(), msgType, e.Process); var json = JsonConvert.SerializeObject(pack); - this.Broadcast(json); + Broadcast(pack); } //直播间分享 - private void Grab_OnShardMessage(object sender, SocialMessage e) + private void Grab_OnShardMessage(object sender, WssBarrageGrab.RoomMessageEventArgs e) { - if (!CheckRoomId(e.Common.roomId)) return; - if (e.Action != 3) return; + var msg = e.Message; + if (!CheckRoomId(msg.Common.roomId)) return; + if (msg.Action != 3) return; ShareType type = ShareType.未知; - if (Enum.IsDefined(type.GetType(), int.Parse(e.shareTarget))) + if (Enum.IsDefined(type.GetType(), int.Parse(msg.shareTarget))) { - type = (ShareType)int.Parse(e.shareTarget); + type = (ShareType)int.Parse(msg.shareTarget); } var enty = new ShareMessage() { - MsgId = e.Common.msgId, - Content = $"{e.User.Nickname} 分享了直播间到{type}", - RoomId = e.Common.roomId, + MsgId = msg.Common.msgId, + Content = $"{msg.User.Nickname} 分享了直播间到{type}", + RoomId = msg.Common.roomId, + WebRoomId = AppRuntime.RoomCaches.GetCachedWebRoomid(msg.Common.roomId.ToString()), ShareType = type, - User = GetUser(e.User) + User = GetUser(msg.User) }; - //shareTarget: (112:好友),(1微信)(2朋友圈)(3微博)(5:qq)(4:qq空间),shareType: 1 - Print($"{enty.User.GenderToString()} {enty.Content}", ConsoleColor.DarkBlue, BarrageMsgType.直播间分享); - var pack = new BarrageMsgPack(JsonConvert.SerializeObject(enty), BarrageMsgType.直播间分享); + + var msgType = PackMsgType.直播间分享; + PrintMsg(enty, msgType); + + //shareTarget: (112:好友),(1微信)(2朋友圈)(3微博)(5:qq)(4:qq空间),shareType: 1 + var pack = new BarrageMsgPack(enty.ToJson(), msgType, e.Process); var json = JsonConvert.SerializeObject(pack); - this.Broadcast(json); + Broadcast(pack); } //来了 - private void Grab_OnMemberMessage(object sender, Modles.ProtoEntity.MemberMessage e) + private void Grab_OnMemberMessage(object sender, WssBarrageGrab.RoomMessageEventArgs e) { - if (!CheckRoomId(e.Common.roomId)) return; + var msg = e.Message; + if (!CheckRoomId(msg.Common.roomId)) return; var enty = new Modles.JsonEntity.MemberMessage() { - MsgId = e.Common.msgId, - Content = $"{e.User.Nickname} 来了 直播间人数:{e.memberCount}", - RoomId = e.Common.roomId, - CurrentCount = e.memberCount, - User = GetUser(e.User) + MsgId = msg.Common.msgId, + Content = $"{msg.User.Nickname} 来了 直播间人数:{msg.memberCount}", + RoomId = msg.Common.roomId, + WebRoomId = AppRuntime.RoomCaches.GetCachedWebRoomid(msg.Common.roomId.ToString()), + CurrentCount = msg.memberCount, + User = GetUser(msg.User) }; - Print($"{enty.User.GenderToString()} {enty.Content}", ConsoleColor.Green, BarrageMsgType.进直播间); - var pack = new BarrageMsgPack(JsonConvert.SerializeObject(enty), BarrageMsgType.进直播间); + + var msgType = PackMsgType.进直播间; + PrintMsg(enty, msgType); + var pack = new BarrageMsgPack(enty.ToJson(), msgType, e.Process); var json = JsonConvert.SerializeObject(pack); - this.Broadcast(json); + Broadcast(pack); } //点赞 - private void Grab_OnLikeMessage(object sender, LikeMessage e) + private void Grab_OnLikeMessage(object sender, WssBarrageGrab.RoomMessageEventArgs e) { - if (!CheckRoomId(e.Common.roomId)) return; + var msg = e.Message; + if (!CheckRoomId(msg.Common.roomId)) return; var enty = new LikeMsg() { - MsgId = e.Common.msgId, - Count = e.Count, - Content = $"{e.User.Nickname} 为主播点了{e.Count}个赞,总点赞{e.Total}", - RoomId = e.Common.roomId, - Total = e.Total, - User = GetUser(e.User) + MsgId = msg.Common.msgId, + Count = msg.Count, + Content = $"{msg.User.Nickname} 为主播点了{msg.Count}个赞,总点赞{msg.Total}", + RoomId = msg.Common.roomId, + WebRoomId = AppRuntime.RoomCaches.GetCachedWebRoomid(msg.Common.roomId.ToString()), + Total = msg.Total, + User = GetUser(msg.User) }; - Print($"{enty.User.GenderToString()} {enty.Content}", ConsoleColor.Cyan, BarrageMsgType.点赞消息); - var pack = new BarrageMsgPack(JsonConvert.SerializeObject(enty), BarrageMsgType.点赞消息); - var json = JsonConvert.SerializeObject(pack); - this.Broadcast(json); + + var msgType = PackMsgType.点赞消息; + PrintMsg(enty, msgType); + var pack = new BarrageMsgPack(enty.ToJson(), msgType, e.Process); + Broadcast(pack); } //弹幕 - private void Grab_OnChatMessage(object sender, ChatMessage e) + private void Grab_OnChatMessage(object sender, WssBarrageGrab.RoomMessageEventArgs e) { - if (!CheckRoomId(e.Common.roomId)) return; + var msg = e.Message; + if (!CheckRoomId(msg.Common.roomId)) return; var enty = new Msg() { - MsgId = e.Common.msgId, - Content = e.Content, - RoomId = e.Common.roomId, - User = GetUser(e.User) + MsgId = msg.Common.msgId, + Content = msg.Content, + RoomId = msg.Common.roomId, + WebRoomId = AppRuntime.RoomCaches.GetCachedWebRoomid(msg.Common.roomId.ToString()), + User = GetUser(msg.User), }; + + var msgType = PackMsgType.弹幕消息; + PrintMsg(enty, msgType); - - Print($"{enty.User.GenderToString()} {enty.User.Nickname}: {enty.Content}", ConsoleColor.White, BarrageMsgType.弹幕消息); - if (e.User.followInfo.followStatus != 1 && e.User.followInfo.followStatus != 0 && e.User.followInfo.followStatus != 2) - { - Console.WriteLine(e.User.followInfo.followStatus); - } - - var pack = new BarrageMsgPack(JsonConvert.SerializeObject(enty), BarrageMsgType.弹幕消息); - var json = JsonConvert.SerializeObject(pack); - this.Broadcast(json); + var pack = new BarrageMsgPack(enty.ToJson(), msgType, e.Process); + Broadcast(pack); } //直播间状态变更 - private void Grab_OnControlMessage(object sender, ControlMessage e) + private void Grab_OnControlMessage(object sender, WssBarrageGrab.RoomMessageEventArgs e) { - if (!CheckRoomId(e.Common.roomId)) return; + var msg = e.Message; + if (!CheckRoomId(msg.Common.roomId)) return; BarrageMsgPack pack = null; //下播 - if (e.Status == 3) + if (msg.Status == 3) { var enty = new Msg() { - MsgId = e.Common.msgId, + MsgId = msg.Common.msgId, Content = "直播已结束", - RoomId = e.Common.roomId, + RoomId = msg.Common.roomId, + WebRoomId = AppRuntime.RoomCaches.GetCachedWebRoomid(msg.Common.roomId.ToString()), User = null }; - Print($"直播已结束", ConsoleColor.DarkCyan, BarrageMsgType.下播); - pack = new BarrageMsgPack(JsonConvert.SerializeObject(enty), BarrageMsgType.下播); + + var msgType = PackMsgType.下播; + PrintMsg(enty, msgType); + pack = new BarrageMsgPack(enty.ToJson(), PackMsgType.下播, e.Process); } if (pack != null) { - var json = JsonConvert.SerializeObject(pack); - this.Broadcast(json); + Broadcast(pack); } } - static int count = 0; - private void Print(string msg, ConsoleColor color, BarrageMsgType bartype) - { - if (!Appsetting.PrintFilter.Any(a => a == bartype.GetHashCode())) return; - if (Appsetting.PrintBarrage) - { - if (++count > 1000) - { - Console.Clear(); - Console.WriteLine("控制台已清理"); - } - console.WriteLine($"{DateTime.Now.ToString("HH:mm:ss")} [{bartype.ToString()}] " + msg + "\n", color); - count = 0; - } - } + //static int count = 0; + //private void Print(string msg, ConsoleColor color, PackMsgType bartype) + //{ + // if (!Appsetting.PrintFilter.Any(a => a == bartype.GetHashCode())) return; + // if (Appsetting.PrintBarrage) + // { + // if (++count > 1000) + // { + // Console.Clear(); + // Console.WriteLine("控制台已清理"); + // } + // console.WriteLine($"{DateTime.Now.ToString("HH:mm:ss")} [{bartype.ToString()}] " + msg + "\n", color); + // count = 0; + // } + //} private void Dieout_Elapsed(object sender, ElapsedEventArgs e) { @@ -442,16 +503,24 @@ namespace BarrageGrab /// 广播消息 /// /// - public void Broadcast(string msg) + public void Broadcast(BarrageMsgPack pack) { - foreach (var user in socketList) + if (pack == null) return; + var offLines = new List(); + foreach (var item in socketList) { - var socket = user.Value; - socket.Socket.Send(msg); + var state = item.Value; + if (item.Value.Socket.IsAvailable) + { + state.Socket.Send(pack.ToJson()); + } + else + { + offLines.Add(item.Key); + } } - //删除掉线的套接字 - var offlines = socketList.Where(w => !w.Value.Socket.IsAvailable).Select(s => s.Key).ToList(); - offlines.ForEach(key => socketList.Remove(key)); + //删除掉线的套接字 + offLines.ForEach(key => socketList.Remove(key)); } /// diff --git a/BarrageGrab/WssBarrageGrab.cs b/BarrageGrab/WssBarrageGrab.cs index 2bce1eb..d6b2494 100644 --- a/BarrageGrab/WssBarrageGrab.cs +++ b/BarrageGrab/WssBarrageGrab.cs @@ -28,48 +28,48 @@ namespace BarrageGrab /// /// 进入直播间 /// - public event EventHandler OnMemberMessage; + public event EventHandler> OnMemberMessage; /// /// 关注 /// - public event EventHandler OnSocialMessage; + public event EventHandler> OnSocialMessage; /// /// 聊天 /// - public event EventHandler OnChatMessage; + public event EventHandler> OnChatMessage; /// /// 点赞 /// - public event EventHandler OnLikeMessage; + public event EventHandler> OnLikeMessage; /// /// 礼物 /// - public event EventHandler OnGiftMessage; + public event EventHandler> OnGiftMessage; /// /// 直播间统计 /// - public event EventHandler OnRoomUserSeqMessage; + public event EventHandler> OnRoomUserSeqMessage; /// /// 直播间状态变更 /// - public event EventHandler OnControlMessage; + public event EventHandler> OnControlMessage; /// /// 粉丝团消息 /// - public event EventHandler OnFansclubMessage; + public event EventHandler> OnFansclubMessage; public WssBarrageGrab() { proxy.OnWebSocketData += Proxy_OnWebSocketData; proxy.OnFetchResponse += Proxy_OnFetchResponse; - } + } public void Start() { @@ -128,7 +128,7 @@ namespace BarrageGrab succPackHostNames.Add(e.HostName); SaveHostNameCache(); } - response.Messages.ForEach(f => DoMessage(f)); + response.Messages.ForEach(f => DoMessage(f,e.ProcessName)); } catch (Exception) { } } @@ -143,18 +143,18 @@ namespace BarrageGrab { succPackHostNames.Add(e.HostName); SaveHostNameCache(); - } + } response.Messages.ForEach(f => { - DoMessage(f); + DoMessage(f,e.ProcessName); }); - } + } //用于缓存接收过的消息ID,判断是否重复接收 Dictionary> msgDic = new Dictionary>(); //发送事件 - private void DoMessage(Message msg) + private void DoMessage(Message msg,string processName) { List msgIdList; if (msgDic.ContainsKey(msg.Method)) @@ -182,28 +182,28 @@ namespace BarrageGrab case "WebcastMemberMessage": { var arg = Serializer.Deserialize(new ReadOnlyMemory(msg.Payload)); - this.OnMemberMessage?.Invoke(this, arg); + this.OnMemberMessage?.Invoke(this, new RoomMessageEventArgs(processName,arg)); break; } //关注 case "WebcastSocialMessage": { - var arg = Serializer.Deserialize(new ReadOnlyMemory(msg.Payload)); - this.OnSocialMessage?.Invoke(this, arg); + var arg = Serializer.Deserialize(new ReadOnlyMemory(msg.Payload)); + this.OnSocialMessage?.Invoke(this, new RoomMessageEventArgs(processName, arg)); break; } //消息 case "WebcastChatMessage": { var arg = Serializer.Deserialize(new ReadOnlyMemory(msg.Payload)); - this.OnChatMessage?.Invoke(this, arg); + this.OnChatMessage?.Invoke(this, new RoomMessageEventArgs(processName, arg)); break; } //点赞 case "WebcastLikeMessage": { var arg = Serializer.Deserialize(new ReadOnlyMemory(msg.Payload)); - this.OnLikeMessage?.Invoke(this, arg); + this.OnLikeMessage?.Invoke(this, new RoomMessageEventArgs(processName, arg)); break; } //礼物 @@ -247,28 +247,28 @@ namespace BarrageGrab } } - this.OnGiftMessage?.Invoke(this, arg); + this.OnGiftMessage?.Invoke(this, new RoomMessageEventArgs(processName, arg)); break; } //直播间统计 case "WebcastRoomUserSeqMessage": { var arg = Serializer.Deserialize(new ReadOnlyMemory(msg.Payload)); - this.OnRoomUserSeqMessage?.Invoke(this, arg); + this.OnRoomUserSeqMessage?.Invoke(this, new RoomMessageEventArgs(processName, arg)); break; } //直播间状态变更 case "WebcastControlMessage": { var arg = Serializer.Deserialize(new ReadOnlyMemory(msg.Payload)); - this.OnControlMessage?.Invoke(this, arg); + this.OnControlMessage?.Invoke(this, new RoomMessageEventArgs(processName, arg)); break; } //粉丝团消息 case "WebcastFansclubMessage": { var arg = Serializer.Deserialize(new ReadOnlyMemory(msg.Payload)); - this.OnFansclubMessage?.Invoke(this, arg); + this.OnFansclubMessage?.Invoke(this, new RoomMessageEventArgs(processName, arg)); break; } default: @@ -309,5 +309,31 @@ namespace BarrageGrab Console.WriteLine("写入成功解包域名缓存失败:" + ex.Message, ConsoleColor.Red); } } + + + public class RoomMessageEventArgs : EventArgs where T : class + { + /// + /// 进程名 + /// + public string Process { get; set; } + + /// + /// 消息 + /// + public T Message { get; set; } + + + public RoomMessageEventArgs() + { + + } + + public RoomMessageEventArgs(string process, T data) + { + this.Process = process; + this.Message = data; + } + } } } diff --git a/BarrageGrab/WssBarrageService.csproj b/BarrageGrab/WssBarrageService.csproj index 34574bd..7c84212 100644 --- a/BarrageGrab/WssBarrageService.csproj +++ b/BarrageGrab/WssBarrageService.csproj @@ -143,6 +143,7 @@ + diff --git a/README.md b/README.md index 09e6cbb..3afff84 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,12 @@ ## ⛳近期更新 +2023-08-17 v2.6.8 + +1. 添加了上游代理支持,现在可以将无关的请求转发到其他的代理地址!详见下方配置说明`upstreamProxy` +2. 内部添加了webRoomid 的缓存映射,只要打开直播间地址即可缓存WebRoomId和Roomid的映射关系,`WebRoomId`现已添加至ws弹幕流数据中!,需要注意直播伴侣端提取的弹幕当前不会有该字段,除非在开播后打开浏览器访问一次你的直播间 +3. MsgPack 现在添加了进程名字段`ProcessName` + 2023-08-17 v2.6.7 1. [重要更新] 支持浏览器/客户端 http 弹幕监听,这也会解决原先版本因网络波动使抖音客户端弹幕获取方式降级,而获取不到弹幕导致必须刷新页面重新连接的问题。 @@ -51,27 +57,30 @@ 程序中有基本的配置可以过滤弹幕进程,弹幕数据通过Websocket服务推送,其他程序只需接入ws服务器就能接收到到弹幕数据消息 ``` xml + - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + ``` @@ -125,4 +134,14 @@ ## ⚖️免责声明 -+ 本程序仅供学习参考,不得用于商业用途,不得用于恶意搜集他人直播间用户信息! \ No newline at end of file ++ 本程序仅供学习参考,不得用于商业用途,不得用于恶意搜集他人直播间用户信息! + +## 🍻赞助支持 + +开源不易,如果该程序能够帮助到你,你的支持将会是我保持迭代的动力🍻! + + +

+微信支付 +支付宝 +

\ No newline at end of file