feat(core): add onDanmakuHandshake userscript event

This commit is contained in:
genteure 2023-07-06 06:37:48 +08:00
parent 324c41ab1d
commit 8a81450d80
6 changed files with 96 additions and 8 deletions

View File

@ -31,6 +31,8 @@ namespace BililiveRecorder.Core.Api.Danmaku
public event EventHandler<StatusChangedEventArgs>? StatusChanged;
public event EventHandler<DanmakuReceivedEventArgs>? DanmakuReceived;
public Func<string, string?>? BeforeHandshake { get; set; } = null;
public DanmakuClient(IDanmakuServerApiClient apiClient, ILogger logger)
{
this.apiClient = apiClient ?? throw new ArgumentNullException(nameof(apiClient));
@ -96,7 +98,7 @@ namespace BililiveRecorder.Core.Api.Danmaku
this.danmakuTransport = transport;
await this.SendHelloAsync(roomid, apiClient.GetUid(), danmakuServerInfo.Token ?? string.Empty).ConfigureAwait(false);
await this.SendHelloAsync(roomid, this.apiClient.GetUid(), danmakuServerInfo.Token ?? string.Empty).ConfigureAwait(false);
await this.SendPingAsync().ConfigureAwait(false);
if (cancellationToken.IsCancellationRequested)
@ -211,8 +213,9 @@ namespace BililiveRecorder.Core.Api.Danmaku
#region Send
private Task SendHelloAsync(int roomid, long uid, string token) =>
this.SendMessageAsync(7, JsonConvert.SerializeObject(new
private Task SendHelloAsync(int roomid, long uid, string token)
{
var body = JsonConvert.SerializeObject(new
{
uid = uid,
roomid = roomid,
@ -220,7 +223,20 @@ namespace BililiveRecorder.Core.Api.Danmaku
platform = "web",
type = 2,
key = token,
}, Formatting.None));
}, Formatting.None);
if (this.BeforeHandshake is { } func)
{
var newBody = func(body);
if (newBody is not null)
{
this.logger.Debug("Danmaku BeforeHandshake: {OldBody} => {NewBody}", body, newBody);
body = newBody;
}
}
return this.SendMessageAsync(7, body);
}
private Task SendPingAsync() => this.SendMessageAsync(2);

View File

@ -13,6 +13,8 @@ namespace BililiveRecorder.Core.Api
event EventHandler<StatusChangedEventArgs>? StatusChanged;
event EventHandler<DanmakuReceivedEventArgs>? DanmakuReceived;
Func<string, string?>? BeforeHandshake { get; set; }
Task ConnectAsync(int roomid, DanmakuTransportMode transportMode, CancellationToken cancellationToken);
Task DisconnectAsync();
}

View File

@ -11,6 +11,7 @@ using BililiveRecorder.Core.Config.V3;
using BililiveRecorder.Core.Danmaku;
using BililiveRecorder.Core.Event;
using BililiveRecorder.Core.Recording;
using BililiveRecorder.Core.Scripting;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json.Linq;
using Polly;
@ -35,6 +36,7 @@ namespace BililiveRecorder.Core
private readonly IApiClient apiClient;
private readonly IBasicDanmakuWriter basicDanmakuWriter;
private readonly IRecordTaskFactory recordTaskFactory;
private readonly UserScriptRunner userScriptRunner;
private readonly CancellationTokenSource cts;
private readonly CancellationToken ct;
@ -63,7 +65,7 @@ namespace BililiveRecorder.Core
coverDownloadHttpClient.DefaultRequestHeaders.UserAgent.Clear();
}
public Room(IServiceScope scope, RoomConfig roomConfig, int initDelayFactor, ILogger logger, IDanmakuClient danmakuClient, IApiClient apiClient, IBasicDanmakuWriter basicDanmakuWriter, IRecordTaskFactory recordTaskFactory)
public Room(IServiceScope scope, RoomConfig roomConfig, int initDelayFactor, ILogger logger, IDanmakuClient danmakuClient, IApiClient apiClient, IBasicDanmakuWriter basicDanmakuWriter, IRecordTaskFactory recordTaskFactory, UserScriptRunner userScriptRunner)
{
this.scope = scope ?? throw new ArgumentNullException(nameof(scope));
this.RoomConfig = roomConfig ?? throw new ArgumentNullException(nameof(roomConfig));
@ -73,6 +75,7 @@ namespace BililiveRecorder.Core
this.apiClient = apiClient ?? throw new ArgumentNullException(nameof(apiClient));
this.basicDanmakuWriter = basicDanmakuWriter ?? throw new ArgumentNullException(nameof(basicDanmakuWriter));
this.recordTaskFactory = recordTaskFactory ?? throw new ArgumentNullException(nameof(recordTaskFactory));
this.userScriptRunner = userScriptRunner ?? throw new ArgumentNullException(nameof(userScriptRunner));
this.timer = new Timer(this.RoomConfig.TimingCheckInterval * 1000d);
this.cts = new CancellationTokenSource();
@ -85,6 +88,7 @@ namespace BililiveRecorder.Core
this.danmakuClient.StatusChanged += this.DanmakuClient_StatusChanged;
this.danmakuClient.DanmakuReceived += this.DanmakuClient_DanmakuReceived;
this.danmakuClient.BeforeHandshake = this.DanmakuClient_BeforeHandshake;
_ = Task.Run(async () =>
{
@ -532,6 +536,11 @@ retry:
});
}
private string? DanmakuClient_BeforeHandshake(string json)
{
return this.userScriptRunner.CallOnDanmakuHandshake(this.logger, this, json);
}
private void DanmakuClient_DanmakuReceived(object sender, Api.Danmaku.DanmakuReceivedEventArgs e)
{
var d = e.Danmaku;
@ -564,21 +573,19 @@ retry:
private void DanmakuClient_StatusChanged(object sender, Api.Danmaku.StatusChangedEventArgs e)
{
this.DanmakuConnected = e.Connected;
if (e.Connected)
{
this.DanmakuConnected = true;
this.danmakuClientConnectTime = DateTimeOffset.UtcNow;
this.logger.Information("弹幕服务器已连接");
}
else
{
this.DanmakuConnected = false;
this.logger.Information("与弹幕服务器的连接被断开");
// 如果连接弹幕服务器的时间在至少 1 分钟之前,重连时不需要等待
// 针对偶尔的网络波动的优化,如果偶尔断开了尽快重连,减少漏录的弹幕量
this.StartDamakuConnection(delay: !((DateTimeOffset.UtcNow - this.danmakuClientConnectTime) > danmakuClientReconnectNoDelay));
this.danmakuClientConnectTime = DateTimeOffset.MaxValue;
}
}

View File

@ -0,0 +1,23 @@
using System;
using Jint;
using Jint.Native.Object;
using Jint.Runtime.Descriptors;
namespace BililiveRecorder.Core.Scripting.Runtime
{
internal class JintRoomInfo : ObjectInstance
{
public JintRoomInfo(Engine engine, IRoom room) : base(engine)
{
if (room is null) throw new ArgumentNullException(nameof(room));
this.FastSetProperty("roomId", new PropertyDescriptor(room.RoomConfig.RoomId, false, true, false));
this.FastSetProperty("shortId", new PropertyDescriptor(room.ShortId, false, true, false));
this.FastSetProperty("name", new PropertyDescriptor(room.Name, false, true, false));
this.FastSetProperty("title", new PropertyDescriptor(room.Title, false, true, false));
this.FastSetProperty("areaParent", new PropertyDescriptor(room.AreaNameParent, false, true, false));
this.FastSetProperty("areaChild", new PropertyDescriptor(room.AreaNameChild, false, true, false));
this.FastSetProperty("objectId", new PropertyDescriptor(room.ObjectId.ToString(), false, true, false));
}
}
}

View File

@ -251,5 +251,44 @@ globalThis.recorderEvents = {};
return null;
}
}
/// <summary>
/// 修改发给弹幕服务器的握手包 JSON
/// </summary>
/// <param name="logger">logger</param>
/// <param name="room">对应的直播间</param>
/// <param name="json">原握手包文本 JSON 数据</param>
/// <returns>新的握手包JSON 或 null</returns>
public string? CallOnDanmakuHandshake(ILogger logger, IRoom room, string json)
{
const string callbackName = "onDanmakuHandshake";
var log = BuildLogger(logger);
try
{
var func = this.ExecuteScriptThenGetEventHandler(log, callbackName);
if (func is null)
return null;
var roomInfo = new JintRoomInfo(func.Engine, room);
var result = func.Engine.Call(func, roomInfo, json);
switch (result)
{
case JsString jsString:
return jsString.ToString();
case JsUndefined or JsNull:
return null;
default:
log.Warning($"{RecorderEvents}.{callbackName}() 返回了不支持的类型: {{ValueType}}", result.Type);
return null;
}
}
catch (Exception ex)
{
log.Error(ex, $"执行脚本 {callbackName} 时发生错误");
return null;
}
}
}
}

View File

@ -513,6 +513,7 @@ namespace BililiveRecorder.Core.Scripting
{
public UserScriptRunner(BililiveRecorder.Core.Config.V3.GlobalConfig config) { }
public bool CallOnDanmaku(Serilog.ILogger logger, string json) { }
public string? CallOnDanmakuHandshake(Serilog.ILogger logger, BililiveRecorder.Core.IRoom room, string json) { }
public string? CallOnFetchStreamUrl(Serilog.ILogger logger, int roomid, int[] qnSetting) { }
public void CallOnTest(Serilog.ILogger logger, System.Action<string>? alert) { }
[return: System.Runtime.CompilerServices.TupleElementNames(new string[] {