diff --git a/BarrageGrab/App.config b/BarrageGrab/App.config index e119cab..91a6d61 100644 --- a/BarrageGrab/App.config +++ b/BarrageGrab/App.config @@ -10,6 +10,8 @@ + + @@ -22,8 +24,6 @@ - - @@ -41,11 +41,13 @@ - + + + @@ -53,6 +55,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/BarrageGrab/AppSetting.cs b/BarrageGrab/AppSetting.cs index 5a08010..188c0bf 100644 --- a/BarrageGrab/AppSetting.cs +++ b/BarrageGrab/AppSetting.cs @@ -40,6 +40,7 @@ namespace BarrageGrab PollingInterval = int.Parse(AppSettings["pollingInterval"].Trim()); DisableLivePageScriptCache = bool.Parse(AppSettings["disableLivePageScriptCache"].Trim()); WebRoomIds = AppSettings["webRoomIds"].Trim().Split(',').Where(w => !string.IsNullOrWhiteSpace(w)).ToArray(); + LiveCompanPath = AppSettings["liveCompanPath"].Trim(); ConfigComPort(); ConfigFilter(); @@ -243,5 +244,10 @@ namespace BarrageGrab /// 是否启用串口过滤器脚本 /// public bool UseComPortFilter { get; set; } + + /// + /// 直播伴侣文件位置 + /// + public string LiveCompanPath { get; set; } = string.Empty; } } diff --git a/BarrageGrab/FodyWeavers.xml b/BarrageGrab/FodyWeavers.xml new file mode 100644 index 0000000..a5dcf04 --- /dev/null +++ b/BarrageGrab/FodyWeavers.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/BarrageGrab/LiveCompanHelper.cs b/BarrageGrab/LiveCompanHelper.cs new file mode 100644 index 0000000..7785ce0 --- /dev/null +++ b/BarrageGrab/LiveCompanHelper.cs @@ -0,0 +1,173 @@ +using HtmlAgilityPack; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using System.Runtime.InteropServices; +using IWshRuntimeLibrary; +using File = System.IO.File; + +namespace BarrageGrab +{ + public static class LiveCompanHelper + { + /// + /// 获取抖音直播伴侣的exe路径 + /// + /// + public static string GetExePath() + { + string exePath = ""; + //获取exe所在目录 + //1.先在开始菜单找 + var shortcutPath = @"C:\ProgramData\Microsoft\Windows\Start Menu\Programs\直播伴侣.lnk"; + if (File.Exists(shortcutPath)) + { + //获取快捷方式的目标路径 + exePath = GetInkTargetPath(shortcutPath); + if (!File.Exists(exePath)) + { + exePath = ""; + Logger.LogWarn($"直播伴侣.lnk 所指向的exe文件位置 {exePath} 无效"); + } + } + else + { + Logger.LogInfo("未找到直播伴侣的桌面快捷方式"); + } + + //再从配置文件读取 + if (exePath.IsNullOrWhiteSpace()) + { + exePath = AppSetting.Current.LiveCompanPath; + if(!exePath.IsNullOrWhiteSpace() && !File.Exists(exePath)) + { + Logger.LogWarn($"所配置的 {exePath} 不存在"); + exePath = ""; + } + } + return exePath; + } + + /// + /// 初始化直播伴侣环境 + /// + public static void SwitchSetup() + { + var exePath = GetExePath(); + if (string.IsNullOrEmpty(exePath)) + { + Logger.LogWarn("未找到直播伴侣.exe文件,跳过环境设置"); + return; + } + + //设置index.js + var indexJsPath = Path.Combine(Path.GetDirectoryName(exePath), "resources", "app", "index.js"); + Logger.LogInfo($"正在配置 " + indexJsPath); + + if (!File.Exists(indexJsPath)) + { + throw new Exception("未找到直播伴侣的index.js文件"); + } + var indexJs = File.ReadAllText(indexJsPath, Encoding.UTF8); + + CheckBackFile(indexJsPath); + var newjs = SetIndexJsContent(indexJs); + if (newjs != indexJs && !newjs.IsNullOrWhiteSpace()) + { + File.WriteAllText(indexJsPath, newjs); + } + + Logger.LogInfo("直播伴侣环境初始化完成"); + } + + //获取Ink快捷方式的目标路径 + private static string GetInkTargetPath(string shortcutPath) + { + // 创建 Windows Script Host 对象 + WshShell shell = new WshShell(); + + // 使用 IWshShortcut 接口加载 .ink 文件 + IWshShortcut shortcut = (IWshShortcut)shell.CreateShortcut(shortcutPath); + + // 获取目标路径 + string targetPath = shortcut.TargetPath; + + return targetPath; + } + + //检测备份文件 + private static void CheckBackFile(string filePath) + { + if (!File.Exists(filePath)) return; + + var bakPath = filePath + ".bak"; + if (!File.Exists(bakPath)) + { + //拷贝一份备份 + File.Copy(filePath, bakPath); + Logger.LogInfo($"已备份 {filePath} -> {bakPath}"); + } + } + + //设置index.js内容 + private static string SetIndexJsContent(string content) + { + //检测注入代理 + var mineProxyHost = $"127.0.0.1:{AppSetting.Current.ProxyPort},direct://"; + SetSwitch("proxy-server", mineProxyHost, ref content); + + //移除文件损坏检测校验 + var checkReg = new Regex(@"if\(\(0,\w.integrityCheckReport\)\(\w\),!\w\.ok\)"); + if (checkReg.IsMatch(content)) + { + content = checkReg.Replace(content, "if(false)"); + Logger.LogInfo($"直播伴侣文件改动检测已拦截"); + } + + return content; + } + + //添加 electron 启动参数 + private static void SetSwitch(string name, string value, ref string content) + { + if (name.IsNullOrWhiteSpace()) return; + + //检测注入 + var proxyReg = new Regex($@"(?\w+)\.commandLine\.appendSwitch\(""{name}"",""(?[^""]*)""\)"); + var proxyMatch = proxyReg.Match(content); + //检测到已经存在配置,则更新参数值 + if (proxyMatch.Success) + { + var matchValue = proxyMatch.Groups["value"].Value; + if (value != matchValue) + { + content = proxyReg.Replace(content, $@"${{varname}}.commandLine.appendSwitch(""{name}"",""{value}"")"); + Logger.LogInfo($"直播伴侣成功覆盖启动参数 [{name}] = [{value}]"); + } + } + //否则添加新的配置 + else + { + var nosandboxReg = new Regex(@"(?\w+)\.commandLine\.appendSwitch\(""no-sandbox""\)"); + var match = nosandboxReg.Match(content); + if (match.Success) + { + var newvalue = $@"{match.Groups["varname"].ToString()}.commandLine.appendSwitch(""{name}"",""{value}""),"; + content = content.Insert(match.Index, newvalue); + Logger.LogInfo($"直播伴侣成功添加启动参数 [{name}] = [{value}]"); + } + } + } + + private static void SetInnerText(HtmlNode node, string text) + { + node.InnerHtml = ""; + var textNode = node.OwnerDocument.CreateTextNode(text); + node.AppendChild(textNode); + } + } +} diff --git a/BarrageGrab/ObjectExtension.cs b/BarrageGrab/ObjectExtension.cs index 3b5b7a7..916578d 100644 --- a/BarrageGrab/ObjectExtension.cs +++ b/BarrageGrab/ObjectExtension.cs @@ -91,7 +91,28 @@ namespace BarrageGrab foreach (var item in list) func(item, i++); } - + /// + /// 是否包含 + /// + /// + /// + /// + public static bool In(this T obj, params T[] args) + { + return args.Contains(obj); + } + + /// + /// 是否包含 + /// + /// + /// + /// + public static bool LikeIn(this string str, params string[] args) + { + return args.Any(a => str.Contains(a)); + } + public static CookieCollection ToCookies(this string cookieStr, string domain) { var cookies = new CookieCollection(); @@ -170,6 +191,133 @@ namespace BarrageGrab if (cookies == null) return; var cookieCollection = dic.ToCookies(domain); cookies.Add(cookieCollection); - } + } + + /// + /// 将字典转为查询参数 + /// + /// + /// + public static string ToQueryParam(this IDictionary dic, bool encode = true) + { + if (dic == null || dic.Count == 0) + return string.Empty; + + var queryParams = dic.Select(kvp => kvp.Key + "=" + (encode ? WebUtility.UrlEncode(kvp.Value) : kvp.Value)); + return string.Join("&", queryParams); + + } + + /// + /// 赋值字典,没有则添加,有则覆盖 + /// + /// + /// + /// + /// + /// + public static void Set(this IDictionary dic, TKey key, TValue value) + { + if (dic.ContainsKey(key)) + { + dic[key] = value; + } + else + { + dic.Add(key, value); + } + } + + /// + /// 赋值字典,没有则添加,有则覆盖 + /// + /// + /// + /// + /// + public static void Set(this IDictionary dic, KeyValuePair kv) + { + dic.Set(kv.Key, kv.Value); + } + + /// + /// 获取Uri查询参数 + /// + /// + /// + /// + public static IDictionary GetQueryParams(this UriBuilder uri) + { + if (uri == null) + { + throw new ArgumentNullException(nameof(uri)); + } + + var queryParams = uri.Query.TrimStart('?'); + + if (string.IsNullOrEmpty(queryParams)) + { + return new Dictionary(); + } + + return queryParams + .Split('&') + .Select(param => param.Split('=')) + .ToDictionary( + keyValuePair => Uri.UnescapeDataString(keyValuePair[0]), + keyValuePair => Uri.UnescapeDataString(keyValuePair.Length > 1 ? keyValuePair[1] : "")); + } + + /// + /// + /// + /// + /// + public static void AddQueryParam(this UriBuilder uri, IEnumerable> vals) + { + if (uri == null) return; + var dic = uri.GetQueryParams(); + foreach (var item in vals) + { + dic.Set(item); + } + uri.Query = dic.ToQueryParam(); + } + + /// + /// 删除一个query项 + /// + /// + /// + public static void RemoveQueryParam(this UriBuilder uri, string key) + { + if (uri == null) return; + var dic = uri.GetQueryParams(); + dic.Remove(key); + if (dic.Count > 0) + { + uri.Query = "?" + dic.ToQueryParam(); + } + else + { + uri.Query = ""; + } + } + + /// + /// 添加参数 + /// + /// + /// + /// + /// + public static void AddQueryParam(this UriBuilder uri, string key, string value) + { + if (uri == null) return; + uri.AddQueryParam(new[] { + new KeyValuePair(key,value) + }); + } + } } diff --git a/BarrageGrab/Program.cs b/BarrageGrab/Program.cs index 1e9d92c..8752188 100644 --- a/BarrageGrab/Program.cs +++ b/BarrageGrab/Program.cs @@ -18,19 +18,16 @@ namespace BarrageGrab static bool exited = false; static bool formExited = false; static WinApi.ControlCtrlDelegate controlCtr = ControlCtrlHandle; - + static Mutex mutex = new Mutex(false, "DyBarrageGrab"); static void Main(string[] args) { - //JintTest(); - //Console.WriteLine("结束"); - //Console.ReadKey(); - //return; - if (CheckAlreadyRunning()) + if (!mutex.WaitOne(TimeSpan.Zero, true)) { - Logger.PrintColor("已经有一个监听程序在运行,按任意键退出..."); + Console.WriteLine("另一个实例已在运行。"); Console.ReadKey(); return; } + SetTitle("抖音弹幕监听推送"); try @@ -39,7 +36,7 @@ namespace BarrageGrab } catch (Exception ex) { - Logger.LogError(ex, "程序初始化错误"); + Logger.LogError(ex, $"程序初始化错误,{ex.Message}"); MessageBox.Show(ex.Message, "程序初始化错误", MessageBoxButtons.OK, MessageBoxIcon.Error); exited = true; } @@ -56,12 +53,12 @@ namespace BarrageGrab Logger.PrintColor("服务器已关闭..."); WinApi.SetConsoleCtrlHandler(controlCtr, false);//反注册捕获控制台关闭 - } private static void Init() { AppRuntime.Init(); + LiveCompanHelper.SwitchSetup(); WinApi.SetConsoleCtrlHandler(controlCtr, true);//捕获控制台关闭 WinApi.DisableQuickEditMode();//禁用控制台快速编辑模式 AppRuntime.DisplayConsole(!AppSetting.Current.HideConsole);//控制控制台可见 @@ -94,8 +91,10 @@ namespace BarrageGrab } AppRuntime.WsServer.StartListen();//启动WS以及代理服务 - Logger.PrintColor($"{AppRuntime.WsServer.ServerLocation} 弹幕服务已启动...",ConsoleColor.Green); - SetTitle($"抖音弹幕监听推送 [{AppRuntime.WsServer.ServerLocation}]"); + Logger.PrintColor($"{AppRuntime.WsServer.ServerLocation} 弹幕服务已启动,其他端可通过此地址获取到弹幕流信息", ConsoleColor.Green); + + Version version = System.Reflection.Assembly.GetAssembly(typeof(Program)).GetName().Version; + SetTitle($"抖音弹幕监听推送 v{version} [{AppRuntime.WsServer.ServerLocation}]"); } //检测设置控制台标题 @@ -123,53 +122,5 @@ namespace BarrageGrab } return false; } - - //检测程序是否多开 - private static bool CheckAlreadyRunning() - { - const string mutexName = "DyBarrageGrab"; - // Try to create a new named mutex. - bool createdNew; - using (Mutex mutex = new Mutex(true, mutexName, out createdNew)) - { - return !createdNew; - } - } -#if DEBUG - private static void JintTest() - { - var jsEng = JsEngine.CreateNewEngine(); - var jsFile = File.ReadAllText(Path.Combine(AppContext.BaseDirectory, "Scripts", "engine", "comPortFilter.js"), Encoding.UTF8); - //var jsFile = @"D:\develop\Project\dy-barrage-grab\BarrageGrab\Scripts\engine\comPortFilter.js"; - jsEng.Execute(jsFile); - var result = jsEng.Invoke("onPackData", 1, new - { - Aporp = 142, - Bprop = "48777", - Cprop = new string[] { "123" }, - Dprop = new Dictionary() - { - { "key1","value1" } - } - },null); - Console.WriteLine($"返回类型:{ result.Type.ToString()},值:{result.ToString()}"); - if (result.Type == Types.String) - { - Console.WriteLine("返回String: " + result.ToString()); - } - if (result.Type == Types.Object) - { - var obj = result.ToObject(); - if (obj is byte[]) - { - Console.WriteLine("返回byte[]"); - } - } - if (result.Type == Types.Symbol) - { - Console.WriteLine("返回Symbol: " + result.ToString()); - } - } -#endif } } diff --git a/BarrageGrab/Properties/AssemblyInfo.cs b/BarrageGrab/Properties/AssemblyInfo.cs index a36c1ed..4b295b0 100644 --- a/BarrageGrab/Properties/AssemblyInfo.cs +++ b/BarrageGrab/Properties/AssemblyInfo.cs @@ -33,6 +33,6 @@ using System.Runtime.InteropServices; //可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值 //通过使用 "*",如下所示: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("2.7.4.0")] -[assembly: AssemblyFileVersion("2.7.4.0")] +[assembly: AssemblyVersion("2.7.6.0")] +[assembly: AssemblyFileVersion("2.7.6.0")] [assembly: NeutralResourcesLanguage("zh-CN")] diff --git a/BarrageGrab/Proxy/TitaniumProxy.cs b/BarrageGrab/Proxy/TitaniumProxy.cs index b202bdc..404190d 100644 --- a/BarrageGrab/Proxy/TitaniumProxy.cs +++ b/BarrageGrab/Proxy/TitaniumProxy.cs @@ -16,6 +16,13 @@ using Titanium.Web.Proxy.EventArguments; using Titanium.Web.Proxy.Http; using Titanium.Web.Proxy.Models; using Titanium.Web.Proxy.StreamExtended.Network; +using System.Security.Cryptography.X509Certificates; +using System.Security.Cryptography; +using HtmlDocument = HtmlAgilityPack.HtmlDocument; +using System.Net.Http; +using System.Text; +using BrotliSharpLib; +using System.IO.Compression; namespace BarrageGrab.Proxy { @@ -65,7 +72,7 @@ namespace BarrageGrab.Proxy Logger.LogWarn("代理环境设置失败:" + ex.Message); } } - + public TitaniumProxy() { //注册系统代理 @@ -75,9 +82,10 @@ namespace BarrageGrab.Proxy proxyServer.ReuseSocket = false; proxyServer.EnableConnectionPool = true; proxyServer.ForwardToUpstreamGateway = true; - proxyServer.CertificateManager.SaveFakeCertificates = true; - proxyServer.CertificateManager.RootCertificate = proxyServer.CertificateManager.LoadRootCertificate(); + proxyServer.CertificateManager.CertificateValidDays = 365 * 10; + proxyServer.CertificateManager.SaveFakeCertificates = true; + proxyServer.CertificateManager.RootCertificate = GetCert(); if (proxyServer.CertificateManager.RootCertificate == null) { Logger.PrintColor("正在进行证书安装,需要信任该证书才可进行https解密,若有提示请确定"); @@ -97,6 +105,64 @@ namespace BarrageGrab.Proxy proxyServer.AddEndPoint(explicitEndPoint); } + private X509Certificate2 GetCert() + { + X509Certificate2 result = proxyServer.CertificateManager.LoadRootCertificate(); + + if (result != null) return result; + + return null; + + // 打开“受信任的根证书颁发机构”存储区 + using (X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine)) + { + store.Open(OpenFlags.ReadOnly); + + // 遍历证书集合 + foreach (X509Certificate2 cert in store.Certificates) + { + try + { + // 尝试使用证书创建 X509Certificate2 实例 + X509Certificate2 certificate = new X509Certificate2(cert); + + //判断过期 + if (DateTime.Now > certificate.NotAfter) continue; + + //判断黑名单 + var black = new string[] { "localhost" }; + if (certificate.FriendlyName.ToLower().LikeIn(black) || + certificate.Subject.ToLower().LikeIn(black) + ) continue; + + + if (certificate.FriendlyName.ToLower().LikeIn("titanium")) + { + result = certificate; + break; + } + + // 打印证书信息 + //Console.WriteLine("证书: " + certificate.Subject); + //Console.WriteLine("颁发者: " + certificate.Issuer); + //Console.WriteLine("有效期: " + certificate.NotBefore + " - " + certificate.NotAfter); + + //result = certificate; + //break; + } + catch (CryptographicException ex) + { + //捕获加密异常,如果证书需要密码,这里会抛出异常 + throw new Exception("证书加载失败: " + ex.Message); + } + } + + store.Close(); + } + + return result; + } + public override void SetUpstreamProxy(string upstreamProxyAddr) { if (string.IsNullOrWhiteSpace(upstreamProxyAddr)) return; @@ -121,6 +187,11 @@ namespace BarrageGrab.Proxy } } + private bool CheckBrowser(string processName) + { + return AppSetting.Current.ProcessFilter.Contains(processName) && processName!="直播伴侣" && processName!= "douyin"; + } + private async Task ProxyServer_BeforeResponse(object sender, SessionEventArgs e) { string uri = e.HttpClient.Request.RequestUri.ToString(); @@ -208,7 +279,7 @@ namespace BarrageGrab.Proxy if (uri.Contains(BARRAGE_POOL_PATH) && contentType.Contains("application/protobuffer")) { var payload = await e.GetResponseBody(); - + base.FireOnFetchResponse(new HttpResponseEventArgs() { HttpClient = e.HttpClient, @@ -274,7 +345,7 @@ namespace BarrageGrab.Proxy }); liveRoomInjectScript = scriptContext + liveRoomInjectScript; var html = await e.GetResponseBodyAsString(); - var doc = new HtmlAgilityPack.HtmlDocument(); + var doc = new HtmlDocument(); doc.LoadHtml(html); if (!liveRoomInjectScript.IsNullOrWhiteSpace()) @@ -311,7 +382,6 @@ namespace BarrageGrab.Proxy AppRuntime.RoomCaches.AddRoomInfoCache(roominfo); } - try { @@ -335,27 +405,11 @@ namespace BarrageGrab.Proxy //给script标签 src加上时间戳避免缓存 if (AppSetting.Current.DisableLivePageScriptCache) { - var scripts = doc.DocumentNode.SelectNodes("//script[@src]"); - if (scripts != null) - { - var ticks = DateTime.Now.Ticks; - foreach (var script in scripts) - { - var src = script.Attributes["src"].Value; - if (src.Contains("?")) - { - src += "&_t=" + ticks; - } - else - { - src += "?_t=" + ticks; - } - script.Attributes["src"].Value = src; - } - html = doc.DocumentNode.OuterHtml; - } + ScriptAddTocks(doc); } + html = doc.DocumentNode.OuterHtml; + e.SetResponseBodyString(html); } @@ -395,6 +449,38 @@ namespace BarrageGrab.Proxy } } + //给部分脚本加上时间戳避免缓存 + private void ScriptAddTocks(HtmlDocument doc) + { + var scripts = doc.DocumentNode.SelectNodes("//script[@src]"); + if (scripts != null) + { + var ticks = DateTime.Now.Ticks; + foreach (var script in scripts) + { + var src = script.Attributes["src"].Value; + + var srcUri = new Uri(src); + if (!CheckHost(srcUri.Host)) continue; + + var fileName = Path.GetFileName(src.Split('?')[0]); + //目前只需要用到相关这些js + if (!fileName.StartsWith("island") && !src.Contains(LIVE_SCRIPT_PATH)) continue; + + if (src.Contains("?")) + { + src += "&_t=" + ticks; + } + else + { + src += "?_t=" + ticks; + } + script.Attributes["src"].Value = src; + } + + } + } + //生成注入上下文 private string BuildContext(IDictionary constVals) { @@ -415,6 +501,7 @@ namespace BarrageGrab.Proxy var processid = e.HttpClient.ProcessId.Value; var processName = base.GetProcessName(processid); var contentType = e.HttpClient.Response.ContentType ?? ""; + var fileName = Path.GetFileName(urlNoQuery); if (contentType == null) return; if (!contentType.Trim().ToLower().Contains("application/javascript")) return; @@ -424,19 +511,17 @@ namespace BarrageGrab.Proxy if (hostname == SCRIPT_HOST //这俩个进程不需要注入 && processName != "直播伴侣" && processName != "douyin" + && fileName.StartsWith("island") ) { var js = await e.GetResponseBodyAsString(); - //var reg2 = new Regex(@"if\(!N.DJ\(\)&&(?\S).current\)\{"); //版本1,已过时 - //if(!(0,k.DJ)()&&_.current){ - - var reg2 = new Regex(@"if\s*\(\s*\!\s*(?[\s\S]+\s*\.\s*DJ\s*\))\s*\(\s*\)\s*&\s*&\s*(?\S)\s*.\s*current\s*\)\s*\{"); + var reg2 = new Regex(@"if\(!\((?\d,\w{1,2})\.DJ\)\(\)&&"); var match = reg2.Match(js); if (match.Success) { - js = reg2.Replace(js, "if(!${v1}()&&${v2}.current){return;"); + js = reg2.Replace(js, "if(false &&"); e.SetResponseBodyString(js); - Logger.PrintColor($"已成功绕过JS页面无操作检测 {urlNoQuery}\n", ConsoleColor.Green); + Logger.PrintColor($"已成功绕过页面无操作检测\n", ConsoleColor.Green); } } @@ -479,6 +564,12 @@ namespace BarrageGrab.Proxy string url = e.HttpClient.Request.RequestUri.ToString(); string hostname = e.HttpClient.Request.RequestUri.Host; + e.DecryptSsl = CheckHost(hostname); + } + + //检测域名白名单 + protected override bool CheckHost(string hostname) + { //需要解析SSL的域名 放在这里,全开会导致性能问题,应只解析业务需要的域名 var decryptSsls = new string[] { @@ -489,24 +580,11 @@ namespace BarrageGrab.Proxy if (decryptSsls.Contains(hostname)) { - e.DecryptSsl = true; - return; + return true; } - else - { - if (!CheckHost(hostname)) - { - e.DecryptSsl = false; - } - } - } - //检测域名白名单 - protected override bool CheckHost(string host) - { - host = host.Trim().ToLower(); - var succ = base.CheckHost(host); - return succ || host == SCRIPT_HOST || host == LIVE_HOST; + hostname = hostname.Trim().ToLower(); + return base.CheckHost(hostname); } //WebSocket 流读取 @@ -585,14 +663,20 @@ namespace BarrageGrab.Proxy /// override public void Start() { - proxyServer.Start(AppSetting.Current.UsedProxy); + proxyServer.Start(false); + if (AppSetting.Current.UsedProxy) { base.RegisterSystemProxy(); + Logger.LogInfo($"系统代理代理已启动,127.0.0.1:{base.ProxyPort}"); //使用其自带的系统代理设置可能会导致格式问题 //proxyServer.SetAsSystemHttpProxy(explicitEndPoint); //proxyServer.SetAsSystemHttpsProxy(explicitEndPoint); } + else + { + Logger.LogInfo($"代理已启动(局域代理),127.0.0.1:{base.ProxyPort}"); + } } } } diff --git a/BarrageGrab/WsBarrageServer.cs b/BarrageGrab/WsBarrageServer.cs index 098dca1..eabb0c2 100644 --- a/BarrageGrab/WsBarrageServer.cs +++ b/BarrageGrab/WsBarrageServer.cs @@ -201,7 +201,7 @@ namespace BarrageGrab private void PrintMsg(Msg msg, PackMsgType barType) { var rinfo = AppRuntime.RoomCaches.GetCachedWebRoomInfo(msg.RoomId.ToString()); - var roomName = (rinfo?.Owner?.Nickname ?? ("直播间" + (msg.WebRoomId.IsNullOrWhiteSpace() ? msg.RoomId.ToString() : msg.WebRoomId))); + var roomName = (rinfo?.Owner?.Nickname ?? ((msg.WebRoomId.IsNullOrWhiteSpace() ? msg.RoomId.ToString() : msg.WebRoomId))); var text = $"{DateTime.Now.ToString("HH:mm:ss")} [{roomName}] [{barType}]"; if (msg.User != null) @@ -347,48 +347,41 @@ namespace BarrageGrab ogift.Name = findForData.name; } - - - //Combo 为1时,表示为可连击礼物 - //if (msg.Gift.Combo) //一堆礼物Combo 和实际可连击情况不匹配,例如亲吻,闪耀星辰 Combo =false,实际上是可连击的,抖音那帮程序员怕是看女主播冲昏头了 - if (true) + //判断礼物重复 + if (msg.repeatEnd == 1 && giftCountCache.ContainsKey(key)) { - //判断礼物重复 - if (msg.repeatEnd == 1) + //清除缓存中的key + if (msg.groupId > 0 ) { - //清除缓存中的key - if (msg.groupId > 0 && giftCountCache.ContainsKey(key)) - { - Tuple _; - giftCountCache.TryRemove(key, out _); - } - return; + Tuple _; + giftCountCache.TryRemove(key, out _); } - var backward = currCount <= lastCount; - if (currCount <= 0) currCount = 1; - - if (giftCountCache.ContainsKey(key)) - { - lastCount = giftCountCache[key].Item1; - backward = currCount <= lastCount; - if (!backward) - { - lock (giftCountCache) - { - giftCountCache[key] = Tuple.Create(currCount, DateTime.Now); - } - } - } - else - { - if (msg.groupId > 0 && !backward) - { - giftCountCache.TryAdd(key, Tuple.Create(currCount, DateTime.Now)); - } - } - //比上次小,则说明先后顺序出了问题,直接丢掉,应为比它大的消息已经处理过了 - if (backward) return; + return; } + var backward = currCount <= lastCount; + if (currCount <= 0) currCount = 1; + + if (giftCountCache.ContainsKey(key)) + { + lastCount = giftCountCache[key].Item1; + backward = currCount <= lastCount; + if (!backward) + { + lock (giftCountCache) + { + giftCountCache[key] = Tuple.Create(currCount, DateTime.Now); + } + } + } + else + { + if (msg.groupId > 0 && !backward) + { + giftCountCache.TryAdd(key, Tuple.Create(currCount, DateTime.Now)); + } + } + //比上次小,则说明先后顺序出了问题,直接丢掉,应为比它大的消息已经处理过了 + if (backward) return; var count = currCount - lastCount; diff --git a/BarrageGrab/WssBarrageService.csproj b/BarrageGrab/WssBarrageService.csproj index 17ed360..e3e1145 100644 --- a/BarrageGrab/WssBarrageService.csproj +++ b/BarrageGrab/WssBarrageService.csproj @@ -1,5 +1,6 @@  + false @@ -29,6 +30,8 @@ 1.0.0.%2a false true + + true @@ -70,6 +73,9 @@ ..\packages\BrotliSharpLib.0.3.3\lib\net451\BrotliSharpLib.dll + + ..\packages\Costura.Fody.5.7.0\lib\netstandard1.0\Costura.dll + ..\packages\Esprima.3.0.4\lib\net462\Esprima.dll True @@ -84,6 +90,9 @@ ..\packages\Jint.3.0.1\lib\net462\Jint.dll True + + ..\packages\Microsoft.Win32.Primitives.4.3.0\lib\net46\Microsoft.Win32.Primitives.dll + ..\packages\Microsoft.Win32.Registry.5.0.0\lib\net461\Microsoft.Win32.Registry.dll @@ -100,29 +109,93 @@ ..\packages\protobuf-net.Core.3.1.22\lib\net462\protobuf-net.Core.dll + + ..\packages\System.AppContext.4.3.0\lib\net46\System.AppContext.dll + ..\packages\System.Buffers.4.5.1\lib\net461\System.Buffers.dll ..\packages\System.Collections.Immutable.1.7.1\lib\net461\System.Collections.Immutable.dll + + + ..\packages\System.Console.4.3.0\lib\net46\System.Console.dll + + + ..\packages\System.Diagnostics.DiagnosticSource.4.3.0\lib\net46\System.Diagnostics.DiagnosticSource.dll + + + ..\packages\System.Diagnostics.Tracing.4.3.0\lib\net462\System.Diagnostics.Tracing.dll + - + + ..\packages\System.Globalization.Calendars.4.3.0\lib\net46\System.Globalization.Calendars.dll + + + ..\packages\System.IO.4.3.0\lib\net462\System.IO.dll + + + ..\packages\System.IO.Compression.4.3.0\lib\net46\System.IO.Compression.dll + + + + ..\packages\System.IO.Compression.ZipFile.4.3.0\lib\net46\System.IO.Compression.ZipFile.dll + + + ..\packages\System.IO.FileSystem.4.3.0\lib\net46\System.IO.FileSystem.dll + + + ..\packages\System.IO.FileSystem.Primitives.4.3.0\lib\net46\System.IO.FileSystem.Primitives.dll + ..\packages\System.Memory.4.5.5\lib\net461\System.Memory.dll + + ..\packages\System.Net.Http.4.3.0\lib\net46\System.Net.Http.dll + + + ..\packages\System.Net.Sockets.4.3.0\lib\net46\System.Net.Sockets.dll + ..\packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll + + ..\packages\System.Reflection.4.3.0\lib\net462\System.Reflection.dll + + + ..\packages\System.Runtime.4.3.0\lib\net462\System.Runtime.dll + ..\packages\System.Runtime.CompilerServices.Unsafe.4.5.3\lib\net461\System.Runtime.CompilerServices.Unsafe.dll + + ..\packages\System.Runtime.Extensions.4.3.0\lib\net462\System.Runtime.Extensions.dll + + + ..\packages\System.Runtime.InteropServices.4.3.0\lib\net462\System.Runtime.InteropServices.dll + + + ..\packages\System.Runtime.InteropServices.RuntimeInformation.4.3.0\lib\net45\System.Runtime.InteropServices.RuntimeInformation.dll + ..\packages\System.Security.AccessControl.5.0.0\lib\net461\System.Security.AccessControl.dll + + ..\packages\System.Security.Cryptography.Algorithms.4.3.0\lib\net461\System.Security.Cryptography.Algorithms.dll + + + ..\packages\System.Security.Cryptography.Encoding.4.3.0\lib\net46\System.Security.Cryptography.Encoding.dll + + + ..\packages\System.Security.Cryptography.Primitives.4.3.0\lib\net46\System.Security.Cryptography.Primitives.dll + + + ..\packages\System.Security.Cryptography.X509Certificates.4.3.0\lib\net461\System.Security.Cryptography.X509Certificates.dll + ..\packages\System.Security.Principal.Windows.5.0.0\lib\net461\System.Security.Principal.Windows.dll @@ -135,8 +208,10 @@ - + + ..\packages\System.Xml.ReaderWriter.4.3.0\lib\net46\System.Xml.ReaderWriter.dll + ..\packages\Titanium.Web.Proxy.3.2.2-beta\lib\net461\Titanium.Web.Proxy.dll @@ -160,6 +235,7 @@ RoomDetail.cs + @@ -194,6 +270,7 @@ + PreserveNewest @@ -221,6 +298,26 @@ RoomDetail.cs - + + + {F935DC20-1CF0-11D0-ADB9-00C04FD58A0B} + 1 + 0 + 0 + tlbimp + False + True + + + + + 这台计算机上缺少此项目引用的 NuGet 程序包。使用“NuGet 程序包还原”可下载这些程序包。有关更多信息,请参见 http://go.microsoft.com/fwlink/?LinkID=322105。缺少的文件是 {0}。 + + + + + + + \ No newline at end of file diff --git a/BarrageGrab/packages.config b/BarrageGrab/packages.config index e0afd29..8fad1c7 100644 --- a/BarrageGrab/packages.config +++ b/BarrageGrab/packages.config @@ -2,23 +2,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Demos/Python/LogWindow.py b/Demos/Python/LogWindow.py new file mode 100644 index 0000000..22e911d --- /dev/null +++ b/Demos/Python/LogWindow.py @@ -0,0 +1,291 @@ +# 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) diff --git a/README.md b/README.md index c9249d0..559d37e 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,30 @@ -# 📺 抖音弹幕监听器 +

📺 抖音弹幕监听器

+ + + +

- 小白不圆 -

## 🔉前文提要 + Github(受网络影响,更新很可能不及时)大佬请移步 [Gitee](https://gitee.com/haodong108/dy-barrage-grab) ,参与Issues讨论,QQ技术讨论群:
- + 群1: 819927029 (已满) + + 群1: 819927029 + 群2: 789875441 (新建) + 注: (进群前请先⭐**Star**,并填入答案,用于排除行外人员,特殊原因请私聊我),之前因满员踢掉的群员可申请群2。 - + 开不起千人群😅 + 发行版下载地址在[这里](https://gitee.com/haodong108/dy-barrage-grab/releases),别下成源码包了!!,**决定下载使用前请先仔细阅读文档**,不要进群提问已经介绍到的问题 -+ 💭 需要**多直播间超长稳定连接**、**自动私信**(支持群组,单发,支持多账号切换),**私信监听**、**自动关注**、**关注,粉丝群,作品点赞,评论监听**、**直播间发弹幕**、**获取榜单**、**直播间,用户主页信息**、**采集商品上架,连麦,PK等其他所有消息**、**作品评论(支持图片)**、 等功能的可以找我(QQ 1083092844)提供一些技术支持 (注:主要面向个人直播自动化,不面向恶意j流,不支持匿名直播间,想搞采集匿名的勿扰,加群也不会有人支持) -+ **LiveIdea** 支持dou音Web全自动化的本地WebApi服务。直播、引留解放双手,需要了解详情请加我 -+ 支持接单,个人定制,dy相关优先,其他方面也可以找我帮忙转发定制,匿名直播间需求勿扰 -+ 那年,我与TA不可告人的秘密😏 ++ 💭 需要抖音日常操作协议化,包括**多直播间超长稳定连接**、**自动私信**(支持群组,单发,支持多账号切换),**私信监听、自动关注、粉丝群、作品点赞评论及监听、直播间发弹幕、获取直播间榜单、直播间更多消息事件、作品评论(支持图片)、发布作品** 等等功能的可以找我(Q1083092844)提供一些技术支持 (注:主要面向个人直播自动化,不面向恶意j流、水jun等,不支持匿名直播间,想搞他人直播间匿名采集的勿扰,加群也不会有人支持) ++ 支持接单、定制等,dy相关优先,其他方面也可以找我帮忙转发定制 ++ 如果好用,请捐赠个仨瓜俩枣,白某谢过各位老板 ## ⛳近期更新 +2024-09-28 v2.7.6 + +1. **[重要更新]** 支持免系统代理监听直播伴侣,需要在配置文件关闭`useproxy`选项,配置后监听web直播间可参考实践指南新部分 +2. 修复了 挂机检测Js拦截逻辑 +3. 默认关闭 禁用JS文件缓存 配置项,提高网页加载速度 +4. 礼物计数相关代码有一小处调整 +5. 整合了dll为单个exe文件 + 2024-05-16 v2.7.5 1. **[重要修复]** 修复了弹幕轮询响应解析Payload读取报错的问题 @@ -55,23 +65,6 @@ 3. 支持了控制台隐藏,推送器弹幕类型过滤,日志弹幕类型过滤,弹幕文件日志 (见配置文件) 4. 支持了更多的ws命令,见 [Command.cs](./BarrageGrab/Modles/JsonEntity/Command.cs) -2023-11-04 v2.6.9 - -1. 修复了因抖音版本升级导致的WebRoomId获取不到的问题,并做了多重处理 - -2023-09-24 v2.6.8 - -1. 添加了上游代理支持,现在可以将无关的请求转发到其他的代理地址!详见下方配置说明`upstreamProxy` -2. 内部添加了webRoomid 的缓存映射,只要打开直播间地址即可缓存WebRoomId和Roomid的映射关系,`WebRoomId`现已添加至ws弹幕流数据中!,需要注意直播伴侣端提取的弹幕当前不会有该字段,除非在开播后打开浏览器访问一次你的直播间 -3. MsgPack 现在添加了进程名字段`ProcessName` - -2023-08-17 v2.6.7 - -1. [重要更新] 支持浏览器/客户端 http 弹幕监听,这也会解决原先版本因网络波动使抖音客户端弹幕获取方式降级,而获取不到弹幕导致必须刷新页面重新连接的问题。 -2. [重要更新] 原先的web/客户端 页面无操作检测逻辑因官方js变动而失效,本次更新修复且完善了该功能,现在会定时模拟全局按键、绕过js检测逻辑、置空Websocket.close 方法三重手段尽量避免断开连接。你也可以直接修改[直播页脚本](./BarrageGrab/Scripts/inject/livePage.js)而无需重新编译源码。 -3. ws连接现在默认监听在0.0.0.0地址,可以从其他客户端连接。 -4. 代理端口现在可选择不占用系统代理,便于某些用户直接使用浏览器代理获取弹幕(启动时携带参数,例如:"chrome.exe --proxy-server=127.0.0.1:8827",也可以在浏览器快捷方式-目标 引号之后附加该参数) - ## 🖼️控制台截图 [![控制台截图](https://s1.ax1x.com/2022/11/10/z9YYPU.png)](https://imgse.com/i/z9YYPU) @@ -98,6 +91,8 @@ + + @@ -110,8 +105,6 @@ - - @@ -134,19 +127,20 @@ + + ``` -### 推送数据格式 - -+ 弹幕数据由WebSocket服务进行分发,使用Json格式进行推送,见项目 [BarrageMessages.cs](./BarrageGrab/Modles/JsonEntity/BarrageMessages.cs),如需调整请克隆项目后参照 [message.proto](./BarrageGrab/proto/message.proto) 进行源码修改调整,文件包含所有弹幕相关数据结构,可前往[ws在线测试](http://wstool.jackxiang.com/)网站,连接 ws://127.0.0.1:8888 进行测试 - ### 使用方法 -1. 管理员身份启动本程序,第一次启动会提示安装自签名证书,程序启动后挂在后台不要关,不然再打开会监听不到正在进行中的直播弹幕。 -2. 打开浏览器进入任何直播间进行测试,没有问题再启动直播伴侣 -3. 如果需要对接自己的应用程序,请先了解 [WebSocket](https://www.bilibili.com/video/BV1KN411n7WD/?share_source=copy_web&vd_source=39ebf42c923024b9eddeca32c9e46d16) ,然后进行对接,可前往[这里](http://wstool.jackxiang.com/)测试 +1. 管理员身份启动本程序,第一次启动会提示安装自签名证书,程序启动后挂在后台不要关,程序无法中途监听已经建立连接的弹幕流 +2. 打开浏览器进入任何直播间,然后观察程序控制台中是否有弹幕滚动 +3. 如果有请往下看,否则请参考诊断排查 +4. 启动后程序控制台标题会显示 ws连接地址,如果需要对接自己的应用程序,请先了解 [WebSocket](https://www.bilibili.com/video/BV1KN411n7WD/?share_source=copy_web&vd_source=39ebf42c923024b9eddeca32c9e46d16) ,然后使用自己的程序进行对接,可前往[在线测试网站](http://wstool.jackxiang.com/)进行连接测试 +5. 对于字段不理解,请参考 [实体类源码文件](./BarrageGrab/Modles/JsonEntity/BarrageMessages.cs) 的注释,或者加群询问 + + - ## 🔌 串口转发 + 配置串口:见配置文件`comPort`,配置发送串口以及波特率,不填则不启用 + 串口报文自定义:可在根目录`Scripts/engine/comPortFilter.js` 根据自己的需求配置转发报文,提供了一定的Bit转换函数,详见其注释 @@ -177,29 +171,59 @@ + **弹幕断连** - + 如果浏览器总是连接一段时间后,就在在控制台看不到弹幕了,优先使用Chrome 和 Edge 浏览器,其他浏览器(例如360浏览器) 可能会存在该问题 - + + 尝试打开配置文件 `forcePolling` 选项,情况获取会有所改善 - + + 其他可能是网络原因,或者直播间断连导致,正在想办法优化解决中。。。需要长时间连接可以找我使用其他方式,支持直连和轮询获取方式 - + **程序关闭后无法上网** + 这是因为本程序运行时会占用系统代理,不正确的关闭方式可能会导致系统代理未能正确关闭,请前往找到 系统设置-网络和Internet-代理-使用代理服务器 并关闭它即可,也可管理员运行[关闭代理.bat](./关闭代理.bat) ++ **未开系统代理,但是无法监听到直播伴侣弹幕** + + + 之前可以突然不行,且刚刚升级直播伴侣,请重监听启程序和直播伴侣再次尝试 + + 如果一次都没成功,则需要检查启动菜单是否有直播伴侣的快捷方式,换言之则必须确保 `C:\ProgramData\Microsoft\Windows\Start Menu\Programs\直播伴侣.lnk` 文件存在,否则请前往配置文件显式指定`直播伴侣.exe`文件存储路径,见配置项`liveCompanPath` + ## 🎲 实践指南 + +### 日常使用 + + **隐藏控制台**:见配置文件 `hideConsole`

+ **远程控制程序**:例如远程关闭,隐藏/显示控制台,启用/关闭系统代理,可发送Json数据包 `{"Cmd":xx,"Data":xx}` 到ws推送地址,数据格式详见: [Command.cs](./BarrageGrab/Modles/JsonEntity/Command.cs)

+ **启用弹幕文件日志**:见配置文件 `barrageFileLog`,启用后,抓到弹幕会记录在根目录 logs/弹幕日志 文件夹中,支持类型过滤,见`logFilter` 配置项

+ **UI界面**: 见配置文件 `showWindow ` (做的仓促,功能简陋)

+ **上游代理**:一般用于自己有 代理工具/代理服务器,希望在使用本程序时访问外网不受到影响,见配置文件项`upstreamProxy` 将它改为你代理工具的代理地址

-+ **局域代理**: 若是只需要监听了浏览器,且不想占用系统代理,可创建一个浏览器快捷方式,打开属性,并在exe文件路径后附加启动参数,`--proxy-server=127.0.0.1:8827`,例如:`"C:\\xxx\chrome.exe" --proxy-server=127.0.0.1:8827`

++ **局域代理**: (请优先参考下文 **浏览器代理切换扩展**) 可创建一个浏览器快捷方式,打开属性,并在exe文件路径后附加启动参数,`--proxy-server=127.0.0.1:8827`,例如:`"C:\\xxx\chrome.exe" --proxy-server=127.0.0.1:8827`

+ **直播间区分**: 推送包含 `Onwer`字段,用户描述主播基本信息,客户端可以优先使用其中的`SecUid`或者`Uid`作为直播间标识,若使用`WebRoomid`则需要注意直播伴侣进程需要特殊处理,因为直播伴侣推送的WebRoomid是用户的抖音号。`RoomId`只能作为场次标识,每次开播都会不一样,不可用作直播间唯一区分

+ **直播间地址**: 冷知识,使用`https://live.douyin.com/<抖音号>` 也可以进入主播直播间 +### 浏览器代理切换扩展 + +若关闭了`usedProxy`开关,浏览器需要通过显示指定代理地址完成代理指向操作,此时可以通过浏览器扩展很好的完成这项功能,以Edge为例 + +1. 前往扩展商店 搜索 `switchyomega` 安装 + + [pA1SSSg.png](https://imgse.com/i/pA1SSSg) + +2. 添加弹幕代理 + + [pA1SCOs.png](https://imgse.com/i/pA1SCOs) + +3. 配置代理地址,并保存,不要配置为ws服务端口地址,程序默认代理端口是**8827** + + [pA1SLjJ.png](https://imgse.com/i/pA1SLjJ) + +4. 进入直播页,应用代理,完成! + + [pA1SXu9.png](https://imgse.com/i/pA1SXu9) + +5. Chrome 用户请自行百度搜索扩展安装方法 + + + ## ⚠️特别注意 @@ -227,8 +251,9 @@ + 请详细阅读[免责声明](./免责声明.txt)条款后进行使用 + ## 🍻支持一下? -开源不易啊老板 +开源不易,老板

微信支付

\ No newline at end of file