This commit is contained in:
Muihd
2024-04-25 23:32:45 +08:00
15 changed files with 683 additions and 200 deletions
+15 -11
View File
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<!--配置更改后重启才能生效-->
<appSettings>
@@ -20,8 +20,8 @@
<add key="pushFilter" value="" />
<!--要日志记录的弹幕消息类型,多个使用','分隔,同上-->
<add key="logFilter" value="1,2,4,5,6,7,8" />
<!--要进行过滤的Web房间ID,多个使用','分隔,根据缓存来过滤的,直播伴侣不支持 -->
<add key="webRoomIds" value="" />
<!--要进行过滤的Web房间ID,多个使用','分隔,根据缓存来过滤的,直播伴侣不支持 -->
<add key="webRoomIds" value="" />
<!--是否启用系统代理,若设置为false 则需要在程序手动指定代理地址 -->
<add key="usedProxy" value="true" />
<!--开启内置的域名过滤,设置为false会解包所有https请求,cpu占用很高,建议在无法获取弹幕数据时调整 -->
@@ -34,14 +34,18 @@
<add key="barrageFileLog" value="false" />
<!--显示窗体-->
<add key="showWindow" value="false" />
<!--进入直播间自动暂停播放-->
<add key="autoPause" value="true"/>
<!--强制启用轮询模式获取弹幕 (对于容易断开连接或者更加追求稳定的直播间,可以启用这个开关,虽然响应速度不如WebSocket,但是绝对稳定!)-->
<add key="forcePolling" value="false"/>
<!--弹幕轮询间隔,当 forcePolling 为 true 时生效 (毫秒,1000毫秒=1秒,不建议小于1000毫秒,太小可能会被封IP,值越小,弹幕流越丝滑,对于观众多的直播间可以改小)-->
<add key="pollingInterval" value="3000"/>
<!--禁用直播页浏览器脚本缓存 (如果需要确保脚本每次能够正常匹配替换,则启用它,可能会损失一定的页面加载速度)-->
<add key="disableLivePageScriptCache" value="true"/>
<!--进入直播间自动暂停播放-->
<add key="autoPause" value="true" />
<!--强制启用轮询模式获取弹幕 (对于容易断开连接或者更加追求稳定的直播间,可以启用这个开关,虽然响应速度不如WebSocket,但是绝对稳定!)-->
<add key="forcePolling" value="false" />
<!--弹幕轮询间隔,当 forcePolling 为 true 时生效 (毫秒,1000毫秒=1秒,不建议小于1000毫秒,太小可能会被封IP,值越小,弹幕流越丝滑,对于观众多的直播间可以改小)-->
<add key="pollingInterval" value="3000" />
<!--禁用直播页浏览器脚本缓存 (如果需要确保脚本每次能够正常匹配替换,则启用它,可能会损失一定的页面加载速度)-->
<add key="disableLivePageScriptCache" value="true" />
<!--指定要发送的COM串口 格式:'COM1:9600' 代表用COM1发送,波特率9600,若不需要使用串口抄送服务请留空 -->
<add key="comPort" value="" />
<!--启用自定义消息过滤,启用后可去程序根目录 Scripts/engine/comPortFilter.js 调整过滤规则 -->
<add key="useComPortFilter" value="true" />
</appSettings>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
+14 -13
View File
@@ -18,24 +18,35 @@ namespace BarrageGrab
/// <summary>
/// Ws弹幕服务示例
/// </summary>
public static WsBarrageService WssService { get; private set; } = new WsBarrageService();
public static WsBarrageServer WsServer { get; private set; }
/// <summary>
/// 房间缓存信息
/// </summary>
public static RoomCacheManager RoomCaches { get; } = new RoomCacheManager();
public static RoomCacheManager RoomCaches { get; private set; }
/// <summary>
/// 串口服务
/// </summary>
public static ComPortServer ComPortServer { get; private set; }
/// <summary>
/// 程序进程信息
/// </summary>
public static Process CurrentProcess { get; private set; } = System.Diagnostics.Process.GetCurrentProcess();
static AppRuntime()
{
}
public static void Init()
{
WsServer = new WsBarrageServer();
RoomCaches = new RoomCacheManager();
ComPortServer = new ComPortServer(WsServer);
}
/// <summary>
/// 隐藏/显示 控制台
/// </summary>
@@ -50,16 +61,6 @@ namespace BarrageGrab
}
}
public static void DisplayConsoleByTitle(bool show)
{
var hWnd = WinApi.FindWindow(null, Console.Title);
//var hWnd = WinApi.GetConsoleWindow();
if (hWnd != IntPtr.Zero)
{
WinApi.ShowWindow(hWnd, show ? WinApi.CmdShow.SW_SHOW : WinApi.CmdShow.SW_HIDE);
}
}
/// <summary>
/// 房间缓存管理器
/// </summary>
@@ -10,13 +10,13 @@ using System.Drawing;
namespace BarrageGrab
{
internal class Appsetting
internal class AppSetting
{
private static readonly Appsetting ins = new Appsetting();
private static readonly AppSetting ins = new AppSetting();
public static Appsetting Current { get { return ins; } }
public static AppSetting Current { get { return ins; } }
public Appsetting()
public AppSetting()
{
try
{
@@ -24,7 +24,7 @@ namespace BarrageGrab
WsProt = int.Parse(AppSettings["wsListenPort"]);
PrintBarrage = AppSettings["printBarrage"].ToLower() == "true";
ProxyPort = int.Parse(AppSettings["proxyPort"]);
PrintFilter = Enum.GetValues(typeof(PackMsgType)).Cast<int>().Where(w=>w>0).ToArray();
PrintFilter = Enum.GetValues(typeof(PackMsgType)).Cast<int>().Where(w => w > 0).ToArray();
PushFilter = Enum.GetValues(typeof(PackMsgType)).Cast<int>().Where(w => w > 0).ToArray();
LogFilter = Enum.GetValues(typeof(PackMsgType)).Cast<int>().Where(w => w > 0).ToArray();
FilterHostName = bool.Parse(AppSettings["filterHostName"].Trim());
@@ -39,26 +39,10 @@ namespace BarrageGrab
ForcePolling = bool.Parse(AppSettings["forcePolling"].Trim());
PollingInterval = int.Parse(AppSettings["pollingInterval"].Trim());
DisableLivePageScriptCache = bool.Parse(AppSettings["disableLivePageScriptCache"].Trim());
WebRoomIds = AppSettings["webRoomIds"].Trim().Split(',').Where(w=>!string.IsNullOrWhiteSpace(w)).Select(x => long.Parse(x)).ToArray();
WebRoomIds = AppSettings["webRoomIds"].Trim().Split(',').Where(w => !string.IsNullOrWhiteSpace(w)).Select(x => long.Parse(x)).ToArray();
var printFilter = AppSettings["printFilter"].Trim().ToLower();
var pushFilter = AppSettings["pushFilter"].Trim().ToLower();
var logFilter = AppSettings["logFilter"].Trim().ToLower();
if (!string.IsNullOrWhiteSpace(printFilter))
{
if (string.IsNullOrWhiteSpace(printFilter)) PrintFilter = new int[0];
else PrintFilter = printFilter.Split(',').Select(x => int.Parse(x)).ToArray();
}
if (!string.IsNullOrWhiteSpace(pushFilter))
{
if (string.IsNullOrWhiteSpace(pushFilter)) PushFilter = new int[0];
else PushFilter = pushFilter.Split(',').Select(x => int.Parse(x)).ToArray();
}
if (!string.IsNullOrWhiteSpace(logFilter))
{
if (string.IsNullOrWhiteSpace(logFilter)) LogFilter = new int[0];
else LogFilter = logFilter.Split(',').Select(x => int.Parse(x)).ToArray();
}
ConfigComPort();
ConfigFilter();
}
catch (Exception ex)
{
@@ -67,11 +51,55 @@ namespace BarrageGrab
}
}
//com串口设置
private void ConfigComPort()
{
var comPort = AppSettings["comPort"];
if (!comPort.IsNullOrWhiteSpace())
{
try
{
var spit = comPort.Split(':');
ComPort = spit[0].Trim();
ComBaudRate = int.Parse(spit[1].Trim());
UseComPortFilter = bool.Parse(AppSettings["useComPortFilter"]);
}
catch (Exception ex)
{
throw new Exception("串口配置格式错误," + ex.Message, ex);
}
}
}
//各种过滤器设置
private void ConfigFilter()
{
var printFilter = AppSettings["printFilter"].Trim().ToLower();
var pushFilter = AppSettings["pushFilter"].Trim().ToLower();
var logFilter = AppSettings["logFilter"].Trim().ToLower();
if (!string.IsNullOrWhiteSpace(printFilter))
{
if (string.IsNullOrWhiteSpace(printFilter)) PrintFilter = new int[0];
else PrintFilter = printFilter.Split(',').Select(x => int.Parse(x)).ToArray();
}
if (!string.IsNullOrWhiteSpace(pushFilter))
{
if (string.IsNullOrWhiteSpace(pushFilter)) PushFilter = new int[0];
else PushFilter = pushFilter.Split(',').Select(x => int.Parse(x)).ToArray();
}
if (!string.IsNullOrWhiteSpace(logFilter))
{
if (string.IsNullOrWhiteSpace(logFilter)) LogFilter = new int[0];
else LogFilter = logFilter.Split(',').Select(x => int.Parse(x)).ToArray();
}
}
public void Save()
{
Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
config.AppSettings.Settings["wsListenPort"].Value = WsProt.ToString();
config.AppSettings.Settings["wsListenPort"].Value = WsProt.ToString();
config.AppSettings.Settings["printBarrage"].Value = PrintBarrage.ToString().ToLower();
config.AppSettings.Settings["printFilter"].Value = string.Join("", PrintFilter);
config.AppSettings.Settings["pushFilter"].Value = string.Join("", PushFilter);
@@ -80,7 +108,7 @@ namespace BarrageGrab
config.AppSettings.Settings["listenAny"].Value = ListenAny.ToString().ToLower();
config.AppSettings.Settings["hideConsole"].Value = HideConsole.ToString().ToLower();
config.AppSettings.Settings["barrageFileLog"].Value = HideConsole.ToString().ToLower();
config.Save(ConfigurationSaveMode.Modified);
ConfigurationManager.RefreshSection(config.AppSettings.SectionInformation.Name);
}
@@ -200,5 +228,20 @@ namespace BarrageGrab
/// 禁用直播页面脚本缓存
/// </summary>
public bool DisableLivePageScriptCache { get; private set; } = true;
/// <summary>
/// 配置的串口
/// </summary>
public string ComPort { get; set; } = string.Empty;
/// <summary>
/// 波特率
/// </summary>
public int ComBaudRate { get; set; } = 9600;
/// <summary>
/// 是否启用串口过滤器脚本
/// </summary>
public bool UseComPortFilter { get; set; }
}
}
+136
View File
@@ -0,0 +1,136 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO.Ports;
using Jint;
using Jint.Runtime;
using System.Windows.Forms;
namespace BarrageGrab
{
public class ComPortServer : IDisposable
{
WsBarrageServer server;
SerialPort sendPort;
static Engine jsEngine = null;
static ComPortServer()
{
if (AppSetting.Current.UseComPortFilter)
{
jsEngine = JsEngine.CreateNewEngine();
var jsFile = JsEngine.GetJsFile("comPortFilter.js");
jsEngine.Execute(jsFile);
}
}
public ComPortServer(WsBarrageServer server)
{
string portName = AppSetting.Current.ComPort;
int baudRate = AppSetting.Current.ComBaudRate;
if (portName.IsNullOrWhiteSpace())
{
return;
//throw new ArgumentNullException(nameof(portName));
}
this.server = server;
server.OnPackMessage += Server_OnPackMessage;
server.OnClose += Server_OnClose;
sendPort = new SerialPort(portName, baudRate);
}
private void Server_OnClose(object sender, EventArgs e)
{
this.Dispose();
}
private void Server_OnPackMessage(WsBarrageServer sender, WsBarrageServer.PackMsgEventArgs e)
{
if (!sendPort.IsOpen) return;
if (e == null) return;
if (!AppSetting.Current.UseComPortFilter)
{
var json = e.ToJson() + "\r\n";
var jbuff = Encoding.UTF8.GetBytes(json);
Send(jbuff);
return;
}
var result = jsEngine.Invoke("onPackData", e.MsgType.GetHashCode(), e.Message);
var nocanTypes = new Types[]
{
Types.Null,
Types.Undefined,
Types.Empty,
Types.Symbol
};
if (nocanTypes.Contains(result.Type)) return;
Func<string, byte[]> encode = (str) => Encoding.UTF8.GetBytes(str);
byte[] buff = new byte[0];
switch (result.Type)
{
case Types.Boolean:
buff = encode(result.AsBoolean().ToString().ToLower());
break;
case Types.String:
buff = encode(result.AsString());
break;
case Types.BigInt:
case Types.Number:
buff = BitConverter.GetBytes(result.AsNumber());
break;
case Types.Object:
if (result.ToObject() is byte[])
{
buff = result.AsArrayBuffer();
}
break;
default:
break;
}
if (buff.Length == 0) return;
Send(buff);
}
//发送数据包
private void Send(byte[] buff)
{
lock (this.sendPort)
{
sendPort.Write(buff, 0, buff.Length);
}
}
/// <summary>
/// 打开串口并开始接收写入数据
/// </summary>
public void OpenStart()
{
if (sendPort == null) throw new Exception("未成功初始化串口对象,请检查配置文件");
try
{
sendPort.Open();
}
catch (Exception ex)
{
sendPort.Close();
sendPort.Dispose();
sendPort = null;
throw ex;
}
}
public void Dispose()
{
if (sendPort == null) return;
sendPort.Close();
sendPort.Dispose();
sendPort = null;
jsEngine.Dispose();
}
}
}
+17 -17
View File
@@ -20,9 +20,9 @@ namespace BarrageGrab
{
static int printCount = 0;
WsBarrageService barServer = AppRuntime.WssService;
WssBarrageGrab grab = AppRuntime.WssService.Grab;
ISystemProxy proxy = AppRuntime.WssService.Grab.Proxy;
WsBarrageServer barServer = AppRuntime.WsServer;
WssBarrageGrab grab = AppRuntime.WsServer.Grab;
ISystemProxy proxy = AppRuntime.WsServer.Grab.Proxy;
public FormView()
{
@@ -32,7 +32,7 @@ namespace BarrageGrab
AppRuntime.RoomCaches.OnCache += RoomCaches_OnCache;
this.txb_wsaddr.Text = barServer.ServerLocation;
this.txb_upstreamProxy.Text = proxy.HttpUpstreamProxy;
this.cbx_barrageLog.Checked = Appsetting.Current.BarrageLog;
this.cbx_barrageLog.Checked = AppSetting.Current.BarrageLog;
InitTabPages();
}
@@ -67,17 +67,17 @@ namespace BarrageGrab
if (page == this.tabPage_Ws)
{
type = "ws";
ck.Checked = Appsetting.Current.PushFilter.Contains(label.Key);
ck.Checked = AppSetting.Current.PushFilter.Contains(label.Key);
}
if (page == this.tabPage_Console)
{
type = "console";
ck.Checked = Appsetting.Current.PrintFilter.Contains(label.Key);
ck.Checked = AppSetting.Current.PrintFilter.Contains(label.Key);
}
if (page == this.tabPage_Log)
{
type = "log";
ck.Checked = Appsetting.Current.LogFilter.Contains(label.Key);
ck.Checked = AppSetting.Current.LogFilter.Contains(label.Key);
}
ck.Name = $"cbx_bartype_{type}_{label.Key}";
ck.CheckedChanged += Ck_CheckedChanged;
@@ -94,17 +94,17 @@ namespace BarrageGrab
var selected = GetCheckedBarTypes((FlowLayoutPanel)cbx.Parent);
if (cbx.Parent.Parent == this.tabPage_Console)
{
Appsetting.Current.PrintFilter = selected;
AppSetting.Current.PrintFilter = selected;
}
if (cbx.Parent.Parent == this.tabPage_Ws)
{
Appsetting.Current.PushFilter = selected;
AppSetting.Current.PushFilter = selected;
}
if(cbx.Parent.Parent == this.tabPage_Log)
{
Appsetting.Current.LogFilter = selected;
AppSetting.Current.LogFilter = selected;
}
Appsetting.Current.Save();
AppSetting.Current.Save();
}
private int[] GetCheckedBarTypes(FlowLayoutPanel panel)
@@ -167,11 +167,11 @@ namespace BarrageGrab
}));
}
private void WssService_OnPrint(object sender, WsBarrageService.PrintEventArgs e)
private void WssService_OnPrint(object sender, WsBarrageServer.PrintEventArgs e)
{
//将弹幕输出到richTextBox
//颜色转换
Color color = Appsetting.Current.ColorMap[e.MsgType].Item2;
Color color = AppSetting.Current.ColorMap[e.MsgType].Item2;
string msg = e.Message;
this.Invoke(new Action(() =>
@@ -218,8 +218,8 @@ namespace BarrageGrab
try
{
proxy.SetUpstreamProxy(text);
Appsetting.Current.UpstreamProxy = text;
Appsetting.Current.Save();
AppSetting.Current.UpstreamProxy = text;
AppSetting.Current.Save();
}
catch (Exception ex)
{
@@ -231,8 +231,8 @@ namespace BarrageGrab
private void cbx_barrageLog_CheckedChanged(object sender, EventArgs e)
{
var checker = (CheckBox)sender;
Appsetting.Current.BarrageLog = checker.Checked;
Appsetting.Current.Save();
AppSetting.Current.BarrageLog = checker.Checked;
AppSetting.Current.Save();
}
}
}
+148
View File
@@ -0,0 +1,148 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using Jint;
using Newtonsoft.Json;
using Jint.Native;
namespace BarrageGrab
{
public static class JsEngine
{
public static Engine CreateNewEngine()
{
var jsonParse = new Func<object, string>((obj) =>
{
try
{
return JsonConvert.SerializeObject(JsonConvert.SerializeObject(obj));
}
catch (Exception ex)
{
Logger.LogError("JS:" + ex.Message);
return null;
}
});
var engine = new Engine();
engine.SetValue("console", new JsConsole());
engine.SetValue("encoder", new JsEncoder(engine));
return engine;
}
/// <summary>
/// 从engine 目录获取js 文件
/// </summary>
/// <param name="fileName"></param>
/// <returns></returns>
public static string GetJsFile(string fileName)
{
var path = Path.Combine(AppContext.BaseDirectory, "Scripts", "engine", fileName);
if (!File.Exists(path)) throw new FileNotFoundException("不存在的文件", path);
return File.ReadAllText(path, Encoding.UTF8);
}
class JsConsole
{
public void log(object obj)
{
if (obj == null) return;
Logger.PrintColor("JS: " + obj.ToString());
}
public void error(object obj)
{
if (obj == null) return;
Logger.LogError("JS: " + obj.ToString());
}
public void warn(object obj)
{
if (obj == null) return;
Logger.LogWarn("JS: " + obj.ToString());
}
}
class JsEncoder
{
Engine eng;
public JsEncoder(Engine eng)
{
this.eng = eng;
eng.Execute(@"
byteToUint8Array = function(arr){
return new Uint8Array(arr);
}");
}
private JsValue ByteConvert(byte[] buff)
{
return eng.Invoke("byteToUint8Array", buff);
}
public JsValue utf8ToBytes(string str)
{
byte[] res = new byte[0];
if (str.IsNullOrWhiteSpace())
{
res = new byte[0];
}
else
{
res = Encoding.UTF8.GetBytes(str);
}
return ByteConvert(res);
}
public JsValue utf32ToBytes(string str)
{
byte[] res = new byte[0];
if (str.IsNullOrWhiteSpace())
{
res = new byte[0];
}
else
{
res = Encoding.UTF32.GetBytes(str);
}
return ByteConvert(res);
}
public JsValue asciiToBytes(string str)
{
byte[] res = new byte[0];
if (str.IsNullOrWhiteSpace())
{
res = new byte[0];
}
else
{
res = Encoding.ASCII.GetBytes(str);
}
return ByteConvert(res);
}
public string utf8ToString(byte[] bytes)
{
if (bytes == null) return null;
return Encoding.UTF8.GetString(bytes);
}
public string utf32ToString(byte[] bytes)
{
if (bytes == null) return null;
return Encoding.UTF32.GetString(bytes);
}
public string asciiToString(byte[] bytes)
{
if (bytes == null) return null;
return Encoding.ASCII.GetString(bytes);
}
}
}
}
+3 -3
View File
@@ -94,9 +94,9 @@ namespace BarrageGrab
/// <param name="msg">弹幕内容</param>
public static void LogBarrage(PackMsgType type, Msg msg)
{
if (!Appsetting.Current.BarrageLog) return;
if (!AppSetting.Current.BarrageLog) return;
if (type == PackMsgType.无) return;
if (Appsetting.Current.LogFilter.Any() && !Appsetting.Current.LogFilter.Contains(type.GetHashCode())) return;
if (AppSetting.Current.LogFilter.Any() && !AppSetting.Current.LogFilter.Contains(type.GetHashCode())) return;
try
{
@@ -135,7 +135,7 @@ namespace BarrageGrab
text += $" [{msg.User?.GenderToString()}] ";
}
ConsoleColor color = Appsetting.Current.ColorMap[barType].Item1;
ConsoleColor color = AppSetting.Current.ColorMap[barType].Item1;
var append = msg.Content;
switch (barType)
{
+104 -47
View File
@@ -6,43 +6,78 @@ using System.Net;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.IO;
using Jint.Runtime;
using System.Windows.Forms;
namespace BarrageGrab
{
public class Program
{
static FormView mainForm = null;
static bool exited = false;
static bool formExited = false;
static WinApi.ControlCtrlDelegate controlCtr = ControlCtrlHandle;
static void Main(string[] args)
{
//JintTest();
//Console.WriteLine("结束");
//Console.ReadKey();
//return;
if (CheckAlreadyRunning())
{
Logger.PrintColor("已经有一个监听程序在运行,按任意键退出...");
Console.ReadKey();
return;
}
SetTitle("抖音弹幕监听推送");
WinApi.SetConsoleCtrlHandler(cancelHandler, true);//捕获控制台关闭
try
{
Init();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "程序初始化错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
exited = true;
}
while (!exited)
{
Thread.Sleep(500);
}
if (!AppRuntime.WsServer.IsDisposed)
{
AppRuntime.WsServer.Dispose();
}
Logger.PrintColor("服务器已关闭...");
WinApi.SetConsoleCtrlHandler(controlCtr, false);//反注册捕获控制台关闭
}
private static void Init()
{
AppRuntime.Init();
WinApi.SetConsoleCtrlHandler(controlCtr, true);//捕获控制台关闭
WinApi.DisableQuickEditMode();//禁用控制台快速编辑模式
if (WinApi.GetConsoleWindow() != IntPtr.Zero)
AppRuntime.DisplayConsole(!AppSetting.Current.HideConsole);//控制控制台可见
AppRuntime.WsServer.Grab.Proxy.SetUpstreamProxy(AppSetting.Current.UpstreamProxy);//设置上游代理
AppRuntime.WsServer.OnClose += (s, e) =>
{
Console.Title = "抖音弹幕监听推送";
}
AppRuntime.DisplayConsole(!Appsetting.Current.HideConsole);
AppRuntime.WssService.Grab.Proxy.SetUpstreamProxy(Appsetting.Current.UpstreamProxy);
exited = true;
};
bool exited = false;
bool formExited = false;
AppRuntime.WssService.StartListen();
Console.ForegroundColor = ConsoleColor.Green;
Logger.LogSucc($"{AppRuntime.WssService.ServerLocation} 弹幕服务已启动...");
Console.ForegroundColor = ConsoleColor.Gray;
if (WinApi.GetConsoleWindow() != IntPtr.Zero)
//串口写入服务
if (!AppSetting.Current.ComPort.IsNullOrWhiteSpace())
{
Console.Title = $"抖音弹幕监听推送 [{AppRuntime.WssService.ServerLocation}]";
AppRuntime.ComPortServer.OpenStart();
}
FormView mainForm = null;
if (Appsetting.Current.ShowWindow)
//显示窗体
if (AppSetting.Current.ShowWindow)
{
var uiThread = new Thread(new ThreadStart(() =>
{
@@ -50,42 +85,29 @@ namespace BarrageGrab
//开启消息循环
System.Windows.Forms.Application.Run(mainForm);
formExited = true;
AppRuntime.WsServer.Dispose();//Close后自动释放资源
}));
uiThread.SetApartmentState(ApartmentState.STA);
uiThread.IsBackground = true;
uiThread.Start();
}
AppRuntime.WssService.OnClose += (s, e) =>
{
//退出程序
exited = true;
};
while (!exited)
{
if (formExited && !exited)
{
AppRuntime.WssService.Close();
}
Thread.Sleep(500);
}
Logger.PrintColor("服务器已关闭...");
//if (!mainForm.IsDisposed)
//{
// mainForm.Invoke(new Action(() =>
// {
// mainForm.Close();
// }));
//}
//退出程序,不显示 按任意键退出
//Environment.Exit(0);
AppRuntime.WsServer.StartListen();//启动WS以及代理服务
Logger.LogSucc($"{AppRuntime.WsServer.ServerLocation} 弹幕服务已启动...");
SetTitle($"抖音弹幕监听推送 [{AppRuntime.WsServer.ServerLocation}]");
}
private static WinApi.ControlCtrlDelegate cancelHandler = new WinApi.ControlCtrlDelegate((CtrlType) =>
//检测设置控制台标题
private static void SetTitle(string title)
{
if (WinApi.GetConsoleWindow() != IntPtr.Zero)
{
Console.Title = title;
}
}
//监听控制台消息事件
private static bool ControlCtrlHandle(int CtrlType)
{
switch (CtrlType)
{
@@ -95,11 +117,11 @@ namespace BarrageGrab
break;
case 2:
Logger.PrintColor("2工具被强制关闭");//按控制台关闭按钮关闭
AppRuntime.WssService.Close();
AppRuntime.WsServer.Dispose();
break;
}
return false;
});
}
//检测程序是否多开
private static bool CheckAlreadyRunning()
@@ -112,5 +134,40 @@ namespace BarrageGrab
return !createdNew;
}
}
private static void JintTest()
{
var jsEng = JsEngine.CreateNewEngine();
var jsFile = File.ReadAllText(Path.Combine(AppContext.BaseDirectory, "Scripts", "engine", "comPortFilter.js"), Encoding.UTF8);
jsEng.Execute(jsFile);
var result = jsEng.Invoke("onPackData", 1, new
{
Aporp = 142,
Bprop = "48777",
Cprop = new string[] { "123" },
Dprop = new Dictionary<string, string>()
{
{ "key1","value1" }
}
});
Console.WriteLine("返回类型:" + result.Type.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());
}
}
}
}
+3 -3
View File
@@ -32,7 +32,7 @@ namespace BarrageGrab.Proxy
/// <summary>
/// 代理端口
/// </summary>
public int ProxyPort { get { return Appsetting.Current.ProxyPort; } }
public int ProxyPort { get { return AppSetting.Current.ProxyPort; } }
public abstract string HttpUpstreamProxy { get; }
@@ -55,11 +55,11 @@ namespace BarrageGrab.Proxy
{
host = host.Trim().ToLower();
if (!Appsetting.Current.FilterHostName) return true;
if (!AppSetting.Current.FilterHostName) return true;
if (host.StartsWith("webcast")) return true;
if (Appsetting.Current.HostNameFilter.Any(a => a.Trim().ToLower() == host)) return true;
if (AppSetting.Current.HostNameFilter.Any(a => a.Trim().ToLower() == host)) return true;
return false;
}
+8 -8
View File
@@ -212,7 +212,7 @@ namespace BarrageGrab.Proxy
var scriptContext = BuildContext(new Dictionary<string, string>()
{
{"PROCESS_NAME","'{processName}'"},
{"AUTOPAUSE",Appsetting.Current.AutoPause.ToString().ToLower()}
{"AUTOPAUSE",AppSetting.Current.AutoPause.ToString().ToLower()}
});
liveRoomInjectScript = scriptContext + liveRoomInjectScript;
var html = await e.GetResponseBodyAsString();
@@ -275,7 +275,7 @@ namespace BarrageGrab.Proxy
}
//给script标签 src加上时间戳避免缓存
if (Appsetting.Current.DisableLivePageScriptCache)
if (AppSetting.Current.DisableLivePageScriptCache)
{
var scripts = doc.DocumentNode.SelectNodes("//script[@src]");
if (scripts != null)
@@ -311,7 +311,7 @@ namespace BarrageGrab.Proxy
var scriptContext = BuildContext(new Dictionary<string, string>()
{
{"PROCESS_NAME","'{processName}'"},
{"AUTOPAUSE",Appsetting.Current.AutoPause.ToString().ToLower()}
{"AUTOPAUSE",AppSetting.Current.AutoPause.ToString().ToLower()}
});
liveHoomInjectScript = scriptContext + liveHoomInjectScript;
@@ -382,7 +382,7 @@ namespace BarrageGrab.Proxy
}
}
if (url.Contains(LIVE_SCRIPT_PATH) && Appsetting.Current.ForcePolling)
if (url.Contains(LIVE_SCRIPT_PATH) && AppSetting.Current.ForcePolling)
{
var reg = new Regex(@"if\s*\((?<patt>!this\.stopPolling)\)");
var js = await e.GetResponseBodyAsString();
@@ -393,7 +393,7 @@ namespace BarrageGrab.Proxy
var pollingIntervalMatch = pollingIntervalReg.Match(js);
if (pollingIntervalMatch.Success)
{
var myValue = Appsetting.Current.PollingInterval;
var myValue = AppSetting.Current.PollingInterval;
js = pollingIntervalReg.Replace(js, $"this.pollingInterval={myValue},");
Logger.PrintColor($"直播间已成功修改轮询间隔为{myValue}ms", ConsoleColor.Green);
}
@@ -515,7 +515,7 @@ namespace BarrageGrab.Proxy
{
proxyServer.Stop();
proxyServer.Dispose();
if (Appsetting.Current.UsedProxy)
if (AppSetting.Current.UsedProxy)
{
CloseSystemProxy();
}
@@ -526,8 +526,8 @@ namespace BarrageGrab.Proxy
/// </summary>
override public void Start()
{
proxyServer.Start(Appsetting.Current.UsedProxy);
if (Appsetting.Current.UsedProxy)
proxyServer.Start(AppSetting.Current.UsedProxy);
if (AppSetting.Current.UsedProxy)
{
proxyServer.SetAsSystemHttpProxy(explicitEndPoint);
proxyServer.SetAsSystemHttpsProxy(explicitEndPoint);
@@ -0,0 +1,28 @@
/**
* 注解:串口数据过滤脚本,可用于自定义配置串口的发送报文
* Tip: 该脚本引擎为Jint,支持的语法请参考官方文档:https://github.com/sebastienros/jint
* 工具环境:
* 1.支持 console.log, console.error, console.warn 这些日志将被输出到控制台, 其中error日志将被记录到日志文件
* 2.编码工具 encoder.utf8ToBytes(str),encoder.utf8ToString 字符串 和 Uint8Array 互转
* encoder.utf32ToBytes(str),encoder.utf32ToString
* encoder.asciiToBytes(str),encoder.asciiToString
*/
/**
* 这个函数由程序JS引擎调用,不要删除
* @param {number} msgType 消息类型,如礼物,弹幕消息等,和程序内定义一致
* @param {Object} msg 被WsBarrageServer简化后的消息对象,具体可参考Json实体文件
* @returns {Uint8Array|string} 返回一个Uint8Array类型的数据,或者返回一个字符串,如果返回 null 则不发送数据。返回的数据将会被发送到串口
*/
function onPackData(msgType, msg) {
let excludeMsgType = [3, 6, 0]; // 过滤掉进入直播间和统计信息
if (excludeMsgType.includes(msgType)) return;
var result = msg.Content + "\r\n";
return result;
// var buff = encoder.utf8ToBytes(result);
// var newjs = encoder.utf8ToString(buff);
// console.log(newjs);
// return newjs;
}
@@ -20,20 +20,44 @@ using System.Collections.Concurrent;
namespace BarrageGrab
{
/// <summary>
/// WsBarrageServer 包装消息事件委托
/// </summary>
public delegate void PackMessageEventHandler(WsBarrageServer sender, WsBarrageServer.PackMsgEventArgs e);
/// <summary>
/// 弹幕服务
/// </summary>
public class WsBarrageService
public class WsBarrageServer:IDisposable
{
WebSocketServer socketServer;
Dictionary<string, UserState> socketList = new Dictionary<string, UserState>();
//礼物计数缓存
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();
Appsetting Appsetting = Appsetting.Current;
bool debug = false;
WebSocketServer socketServer; //Ws服务器对象
Dictionary<string, UserState> socketList = new Dictionary<string, UserState>(); //客户端列表
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();//弹幕解析器核心
AppSetting Appsetting = AppSetting.Current;//全局配置文件实例
static int printCount = 0; //控制台输出计数,用于判断清理控制台
/// <summary>
/// WS服务器启动地址
/// </summary>
public string ServerLocation => socketServer.Location;
/// <summary>
/// 数据内核
/// </summary>
public WssBarrageGrab Grab => grab;
/// <summary>
/// 控制台打印事件
/// </summary>
public event EventHandler<PrintEventArgs> OnPrint;
/// <summary>
/// 是否已经释放资源
/// </summary>
public bool IsDisposed { get; private set; } = false;
/// <summary>
/// 服务关闭后触发
@@ -41,25 +65,12 @@ namespace BarrageGrab
public event EventHandler OnClose;
/// <summary>
/// WS服务器启动地址
/// 消息包装完成后触发
/// </summary>
public string ServerLocation { get { return socketServer.Location; } }
public event PackMessageEventHandler OnPackMessage;
/// <summary>
/// 数据内核
/// </summary>
public WssBarrageGrab Grab { get { return grab; } }
/// <summary>
/// 控制台打印事件
/// </summary>
public event EventHandler<PrintEventArgs> OnPrint;
public WsBarrageService()
public WsBarrageServer()
{
#if DEBUG
debug = true;
#endif
var socket = new WebSocketServer($"ws://0.0.0.0:{Appsetting.WsProt}");
socket.RestartAfterListenError = true;//异常重启
@@ -81,6 +92,7 @@ namespace BarrageGrab
giftCountTimer.Start();
}
//礼物缓存清理计时器回调
private void GiftCountTimer_Elapsed(object sender, ElapsedEventArgs e)
{
var now = DateTime.Now;
@@ -91,20 +103,29 @@ namespace BarrageGrab
{
timeOutKeys.ForEach(key =>
{
Tuple<int, DateTime> _;
giftCountCache.TryRemove(key, out _);
});
}
}
//心跳淘汰计时器回调
private void Dieout_Elapsed(object sender, ElapsedEventArgs e)
{
var now = DateTime.Now;
var dieoutKvs = socketList.Where(w => w.Value.LastPing.AddSeconds(dieout.Interval * 3) < now).ToList();
dieoutKvs.ForEach(f => f.Value.Socket.Close());
}
//判断Rommid是否符合拦截规则
private bool CheckRoomId(long roomid)
{
if (!Appsetting.Current.WebRoomIds.Any()) return true;
if (!AppSetting.Current.WebRoomIds.Any()) return true;
var webrid = AppRuntime.RoomCaches.GetCachedWebRoomid(roomid.ToString());
if (webrid <= 0) return true;
return Appsetting.Current.WebRoomIds.Contains(webrid);
return AppSetting.Current.WebRoomIds.Contains(webrid);
}
//解析用户
@@ -141,7 +162,7 @@ namespace BarrageGrab
return user;
}
static int count = 0;
//打印消息
private void PrintMsg(Msg msg, PackMsgType barType)
{
var rinfo = AppRuntime.RoomCaches.GetCachedWebRoomInfo(msg.RoomId.ToString());
@@ -153,7 +174,7 @@ namespace BarrageGrab
text += $" [{msg.User?.GenderToString()}] ";
}
ConsoleColor color = Appsetting.Current.ColorMap[barType].Item1;
ConsoleColor color = AppSetting.Current.ColorMap[barType].Item1;
var append = msg.Content;
switch (barType)
{
@@ -164,13 +185,13 @@ namespace BarrageGrab
text += append;
if (Appsetting.Current.BarrageLog)
if (AppSetting.Current.BarrageLog)
{
Logger.LogBarrage(barType, msg);
}
if (!Appsetting.PrintBarrage) return;
if (Appsetting.Current.PrintFilter.Any() && !Appsetting.Current.PrintFilter.Contains(barType.GetHashCode())) return;
if (AppSetting.Current.PrintFilter.Any() && !AppSetting.Current.PrintFilter.Contains(barType.GetHashCode())) return;
OnPrint?.Invoke(this, new PrintEventArgs()
{
@@ -179,15 +200,28 @@ namespace BarrageGrab
MsgType = barType
});
if (++count > 10000)
if (++printCount > 10000)
{
Console.Clear();
Logger.PrintColor("控制台已清理");
count = 0;
printCount = 0;
}
Logger.PrintColor(text + "\n", color);
}
//发送消息包装事件
private async void FirePack(Msg msg, PackMsgType barType)
{
if (OnPackMessage == null) return;
var arg = new PackMsgEventArgs()
{
MsgType = barType,
Message = msg
};
//异步执行
OnPackMessage(this, arg);
}
//粉丝团
private void Grab_OnFansclubMessage(object sender, WssBarrageGrab.RoomMessageEventArgs<FansclubMessage> e)
{
@@ -205,6 +239,7 @@ namespace BarrageGrab
enty.Level = enty.User.FansClub.Level;
var msgType = PackMsgType.粉丝团消息;
FirePack(enty, msgType);
PrintMsg(enty, msgType);
Broadcast(new BarrageMsgPack(enty.ToJson(), msgType, e.Process));
}
@@ -228,6 +263,7 @@ namespace BarrageGrab
};
var msgType = PackMsgType.直播间统计;
FirePack(enty, msgType);
PrintMsg(enty, msgType);
var pack = new BarrageMsgPack(enty.ToJson(), msgType, e.Process);
Broadcast(pack);
@@ -252,6 +288,7 @@ namespace BarrageGrab
//清除缓存中的key
if (msg.groupId > 0 && giftCountCache.ContainsKey(key))
{
Tuple<int, DateTime> _;
giftCountCache.TryRemove(key, out _);
}
return;
@@ -297,8 +334,8 @@ namespace BarrageGrab
GiftId = msg.giftId,
GiftName = msg.Gift.Name,
Combo = msg.Gift.Combo,
ImgUrl = msg.Gift.Image?.urlLists?.FirstOrDefault()??"",
User = GetUser(msg.User),
ImgUrl = msg.Gift.Image?.urlLists?.FirstOrDefault() ?? "",
User = GetUser(msg.User),
ToUser = GetUser(msg.toUser)
};
@@ -308,6 +345,7 @@ namespace BarrageGrab
}
var msgType = PackMsgType.礼物消息;
FirePack(enty, msgType);
PrintMsg(enty, msgType);
var pack = new BarrageMsgPack(enty.ToJson(), PackMsgType.礼物消息, e.Process);
Broadcast(pack);
@@ -329,6 +367,7 @@ namespace BarrageGrab
};
var msgType = PackMsgType.关注消息;
FirePack(enty, msgType);
PrintMsg(enty, msgType);
var pack = new BarrageMsgPack(enty.ToJson(), msgType, e.Process);
var json = JsonConvert.SerializeObject(pack);
@@ -358,6 +397,7 @@ namespace BarrageGrab
};
var msgType = PackMsgType.直播间分享;
FirePack(enty, msgType);
PrintMsg(enty, msgType);
//shareTarget: (112:好友),(1微信)(2朋友圈)(3微博)(5:qq)(4:qq空间),shareType: 1
@@ -383,6 +423,7 @@ namespace BarrageGrab
};
var msgType = PackMsgType.进直播间;
FirePack(enty, msgType);
PrintMsg(enty, msgType);
var pack = new BarrageMsgPack(enty.ToJson(), msgType, e.Process);
var json = JsonConvert.SerializeObject(pack);
@@ -407,6 +448,7 @@ namespace BarrageGrab
};
var msgType = PackMsgType.点赞消息;
FirePack(enty, msgType);
PrintMsg(enty, msgType);
var pack = new BarrageMsgPack(enty.ToJson(), msgType, e.Process);
Broadcast(pack);
@@ -428,6 +470,7 @@ namespace BarrageGrab
};
var msgType = PackMsgType.弹幕消息;
FirePack(enty, msgType);
PrintMsg(enty, msgType);
var pack = new BarrageMsgPack(enty.ToJson(), msgType, e.Process);
@@ -453,6 +496,7 @@ namespace BarrageGrab
};
var msgType = PackMsgType.下播;
FirePack(enty, msgType);
PrintMsg(enty, msgType);
pack = new BarrageMsgPack(enty.ToJson(), PackMsgType.下播, e.Process);
}
@@ -463,29 +507,7 @@ namespace BarrageGrab
}
}
//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();
// Logger.PrintColor("控制台已清理");
// }
// Logger.PrintColor($"{DateTime.Now.ToString("HH:mm:ss")} [{bartype.ToString()}] " + msg + "\n", color);
// count = 0;
// }
//}
private void Dieout_Elapsed(object sender, ElapsedEventArgs e)
{
var now = DateTime.Now;
var dieoutKvs = socketList.Where(w => w.Value.LastPing.AddSeconds(dieout.Interval * 3) < now).ToList();
dieoutKvs.ForEach(f => f.Value.Socket.Close());
}
//监听用户连接
private void Listen(IWebSocketConnection socket)
{
//客户端url
@@ -511,7 +533,7 @@ namespace BarrageGrab
switch (cmdPack.Cmd)
{
case CommandCode.Close:
this.Close();
this.Dispose();
break;
case CommandCode.EnableProxy:
{
@@ -553,7 +575,7 @@ namespace BarrageGrab
public void Broadcast(BarrageMsgPack pack)
{
if (pack == null) return;
if (Appsetting.Current.PushFilter.Any() && !Appsetting.Current.PushFilter.Contains(pack.Type.GetHashCode())) return;
if (AppSetting.Current.PushFilter.Any() && !AppSetting.Current.PushFilter.Contains(pack.Type.GetHashCode())) return;
var offLines = new List<string>();
foreach (var item in socketList)
@@ -577,23 +599,34 @@ namespace BarrageGrab
/// </summary>
public void StartListen()
{
this.grab.Start(); //启动代理
this.socketServer.Start(Listen);//启动监听
try
{
this.grab.Start(); //启动代理
this.socketServer.Start(Listen);//启动监听
}
catch (Exception)
{
this.Dispose();
throw;
}
}
/// <summary>
/// 关闭服务器连接,并关闭系统代理
/// </summary>
public void Close()
public void Dispose()
{
socketList.Values.ToList().ForEach(f => f.Socket.Close());
socketList.Clear();
socketServer.Dispose();
grab.Dispose();
this.IsDisposed = true;
this.OnClose?.Invoke(this, EventArgs.Empty);
}
/// <summary>
/// 客户端套接字上下文
/// </summary>
class UserState
{
/// <summary>
@@ -616,6 +649,9 @@ namespace BarrageGrab
}
}
/// <summary>
/// Print事件参数
/// </summary>
public class PrintEventArgs : EventArgs
{
public string Message { get; set; }
@@ -624,5 +660,21 @@ namespace BarrageGrab
public PackMsgType MsgType { get; set; }
}
/// <summary>
/// 消息包事件参数
/// </summary>
public class PackMsgEventArgs
{
/// <summary>
/// 消息类型
/// </summary>
public PackMsgType MsgType { get; set; }
/// <summary>
/// 消息内容
/// </summary>
public Msg Message { get; set; }
}
}
}
+1 -1
View File
@@ -19,7 +19,7 @@ namespace BarrageGrab
{
//ISystemProxy proxy = new FiddlerProxy();
ISystemProxy proxy = new TitaniumProxy();
Appsetting appsetting = Appsetting.Current;
AppSetting appsetting = AppSetting.Current;
/// <summary>
/// 进入直播间
+15 -2
View File
@@ -70,12 +70,20 @@
<Reference Include="BrotliSharpLib, Version=0.3.2.0, Culture=neutral, PublicKeyToken=3f4e2a1cd615fcb7, processorArchitecture=MSIL">
<HintPath>..\packages\BrotliSharpLib.0.3.3\lib\net451\BrotliSharpLib.dll</HintPath>
</Reference>
<Reference Include="Esprima, Version=3.0.4.0, Culture=neutral, PublicKeyToken=2e92ba9c8d81157f, processorArchitecture=MSIL">
<HintPath>..\packages\Esprima.3.0.4\lib\net462\Esprima.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Fleck, Version=1.2.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Fleck.1.2.0\lib\net45\Fleck.dll</HintPath>
</Reference>
<Reference Include="HtmlAgilityPack, Version=1.11.51.0, Culture=neutral, PublicKeyToken=bd319b19eaf3b43a, processorArchitecture=MSIL">
<HintPath>..\packages\HtmlAgilityPack.1.11.51\lib\Net45\HtmlAgilityPack.dll</HintPath>
</Reference>
<Reference Include="Jint, Version=3.0.1.0, Culture=neutral, PublicKeyToken=2e92ba9c8d81157f, processorArchitecture=MSIL">
<HintPath>..\packages\Jint.3.0.1\lib\net462\Jint.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.Win32.Registry, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Win32.Registry.5.0.0\lib\net461\Microsoft.Win32.Registry.dll</HintPath>
</Reference>
@@ -135,7 +143,8 @@
</ItemGroup>
<ItemGroup>
<Compile Include="AppRuntime.cs" />
<Compile Include="Appsetting.cs" />
<Compile Include="AppSetting.cs" />
<Compile Include="ComPortServer.cs" />
<Compile Include="Forms\FormView.cs">
<SubType>Form</SubType>
</Compile>
@@ -149,6 +158,7 @@
<Compile Include="Forms\RoomDetail.Designer.cs">
<DependentUpon>RoomDetail.cs</DependentUpon>
</Compile>
<Compile Include="JsEngine.cs" />
<Compile Include="Logger.cs" />
<Compile Include="Modles\BusinessExecption.cs" />
<Compile Include="Modles\JsonEntity\BarrageMessages.cs" />
@@ -167,7 +177,7 @@
<Compile Include="WinApi.cs" />
<Compile Include="WssBarrageGrab.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="WsBarrageService.cs" />
<Compile Include="WsBarrageServer.cs" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
@@ -178,6 +188,9 @@
<None Include="Properties\app.manifest" />
</ItemGroup>
<ItemGroup>
<Content Include="Scripts\engine\comPortFilter.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Scripts\inject\livePage.js">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
+2 -1
View File
@@ -2,9 +2,10 @@
<packages>
<package id="BouncyCastle.Cryptography" version="2.0.0" targetFramework="net48" />
<package id="BrotliSharpLib" version="0.3.3" targetFramework="net48" />
<package id="ColorConsole" version="1.0.1" targetFramework="net48" />
<package id="Esprima" version="3.0.4" targetFramework="net462" />
<package id="Fleck" version="1.2.0" targetFramework="net48" />
<package id="HtmlAgilityPack" version="1.11.51" targetFramework="net48" />
<package id="Jint" version="3.0.1" targetFramework="net462" />
<package id="Microsoft.Win32.Registry" version="5.0.0" targetFramework="net48" />
<package id="Newtonsoft.Json" version="13.0.1" targetFramework="net48" />
<package id="NLog" version="5.2.3" targetFramework="net48" />