Core: Add danmaku filter to userscript

This commit is contained in:
genteure 2022-05-18 00:23:01 +08:00
parent 54e64d108d
commit 69914d6791
4 changed files with 120 additions and 80 deletions

View File

@ -155,15 +155,17 @@ namespace BililiveRecorder.Core.Api.Danmaku
/// <summary>
/// 原始数据
/// </summary>
public string? RawString { get; set; }
public string RawString { get; set; }
/// <summary>
/// 原始数据
/// </summary>
public JObject? RawObject { get; set; }
public DanmakuModel()
{ }
private DanmakuModel()
{
this.RawString = string.Empty;
}
public DanmakuModel(string json)
{

View File

@ -7,6 +7,7 @@ using System.Threading.Tasks;
using System.Xml;
using BililiveRecorder.Core.Api.Danmaku;
using BililiveRecorder.Core.Config.V3;
using BililiveRecorder.Core.Scripting;
using Serilog;
#nullable enable
@ -34,10 +35,12 @@ namespace BililiveRecorder.Core.Danmaku
private readonly SemaphoreSlim semaphoreSlim = new SemaphoreSlim(1, 1);
private readonly ILogger logger;
private readonly UserScriptRunner userScriptRunner;
public BasicDanmakuWriter(ILogger logger)
public BasicDanmakuWriter(ILogger logger, UserScriptRunner userScriptRunner)
{
this.logger = logger?.ForContext<BasicDanmakuWriter>() ?? throw new ArgumentNullException(nameof(logger));
this.userScriptRunner = userScriptRunner ?? throw new ArgumentNullException(nameof(userScriptRunner));
}
public void EnableWithPath(string path, IRoom room)
@ -105,89 +108,98 @@ namespace BililiveRecorder.Core.Danmaku
public async Task WriteAsync(DanmakuModel danmakuModel)
{
if (this.disposedValue) return;
if (this.config is null) return;
if (this.disposedValue)
return;
if (this.xmlWriter is null || this.config is null)
return;
if (danmakuModel.MsgType is not (DanmakuMsgType.Comment or DanmakuMsgType.SuperChat or DanmakuMsgType.GiftSend or DanmakuMsgType.GuardBuy))
return;
if (!this.userScriptRunner.CallOnDanmaku(this.logger, danmakuModel.RawString))
return;
await this.semaphoreSlim.WaitAsync();
try
{
if (this.xmlWriter != null)
if (this.xmlWriter is null)
return;
var write = true;
var recordDanmakuRaw = this.config.RecordDanmakuRaw;
switch (danmakuModel.MsgType)
{
var write = true;
var recordDanmakuRaw = this.config.RecordDanmakuRaw;
switch (danmakuModel.MsgType)
{
case DanmakuMsgType.Comment:
{
var type = danmakuModel.RawObject?["info"]?[0]?[1]?.ToObject<int>() ?? 1;
var size = danmakuModel.RawObject?["info"]?[0]?[2]?.ToObject<int>() ?? 25;
var color = danmakuModel.RawObject?["info"]?[0]?[3]?.ToObject<int>() ?? 0XFFFFFF;
var st = danmakuModel.RawObject?["info"]?[0]?[4]?.ToObject<long>() ?? 0L;
case DanmakuMsgType.Comment:
{
var type = danmakuModel.RawObject?["info"]?[0]?[1]?.ToObject<int>() ?? 1;
var size = danmakuModel.RawObject?["info"]?[0]?[2]?.ToObject<int>() ?? 25;
var color = danmakuModel.RawObject?["info"]?[0]?[3]?.ToObject<int>() ?? 0XFFFFFF;
var st = danmakuModel.RawObject?["info"]?[0]?[4]?.ToObject<long>() ?? 0L;
var ts = Math.Max((DateTimeOffset.UtcNow - this.offset).TotalSeconds, 0d);
var ts = Math.Max((DateTimeOffset.UtcNow - this.offset).TotalSeconds, 0d);
await this.xmlWriter.WriteStartElementAsync(null, "d", null).ConfigureAwait(false);
await this.xmlWriter.WriteAttributeStringAsync(null, "p", null, $"{ts:F3},{type},{size},{color},{st},0,{danmakuModel.UserID},0").ConfigureAwait(false);
await this.xmlWriter.WriteAttributeStringAsync(null, "user", null, RemoveInvalidXMLChars(danmakuModel.UserName)).ConfigureAwait(false);
if (recordDanmakuRaw)
await this.xmlWriter.WriteAttributeStringAsync(null, "raw", null, RemoveInvalidXMLChars(danmakuModel.RawObject?["info"]?.ToString(Newtonsoft.Json.Formatting.None))).ConfigureAwait(false);
this.xmlWriter.WriteValue(RemoveInvalidXMLChars(danmakuModel.CommentText));
await this.xmlWriter.WriteEndElementAsync().ConfigureAwait(false);
}
break;
case DanmakuMsgType.SuperChat:
if (this.config.RecordDanmakuSuperChat)
{
await this.xmlWriter.WriteStartElementAsync(null, "sc", null).ConfigureAwait(false);
var ts = Math.Max((DateTimeOffset.UtcNow - this.offset).TotalSeconds, 0d);
await this.xmlWriter.WriteAttributeStringAsync(null, "ts", null, ts.ToString("F3")).ConfigureAwait(false);
await this.xmlWriter.WriteAttributeStringAsync(null, "user", null, RemoveInvalidXMLChars(danmakuModel.UserName)).ConfigureAwait(false);
await this.xmlWriter.WriteAttributeStringAsync(null, "price", null, danmakuModel.Price.ToString()).ConfigureAwait(false);
await this.xmlWriter.WriteAttributeStringAsync(null, "time", null, danmakuModel.SCKeepTime.ToString()).ConfigureAwait(false);
if (recordDanmakuRaw)
await this.xmlWriter.WriteAttributeStringAsync(null, "raw", null, RemoveInvalidXMLChars(danmakuModel.RawObject?["data"]?.ToString(Newtonsoft.Json.Formatting.None))).ConfigureAwait(false);
this.xmlWriter.WriteValue(RemoveInvalidXMLChars(danmakuModel.CommentText));
await this.xmlWriter.WriteEndElementAsync().ConfigureAwait(false);
}
break;
case DanmakuMsgType.GiftSend:
if (this.config.RecordDanmakuGift)
{
await this.xmlWriter.WriteStartElementAsync(null, "gift", null).ConfigureAwait(false);
var ts = Math.Max((DateTimeOffset.UtcNow - this.offset).TotalSeconds, 0d);
await this.xmlWriter.WriteAttributeStringAsync(null, "ts", null, ts.ToString("F3")).ConfigureAwait(false);
await this.xmlWriter.WriteAttributeStringAsync(null, "user", null, RemoveInvalidXMLChars(danmakuModel.UserName)).ConfigureAwait(false);
await this.xmlWriter.WriteAttributeStringAsync(null, "giftname", null, RemoveInvalidXMLChars(danmakuModel.GiftName)).ConfigureAwait(false);
await this.xmlWriter.WriteAttributeStringAsync(null, "giftcount", null, danmakuModel.GiftCount.ToString()).ConfigureAwait(false);
if (recordDanmakuRaw)
await this.xmlWriter.WriteAttributeStringAsync(null, "raw", null, RemoveInvalidXMLChars(danmakuModel.RawObject?["data"]?.ToString(Newtonsoft.Json.Formatting.None))).ConfigureAwait(false);
await this.xmlWriter.WriteEndElementAsync().ConfigureAwait(false);
}
break;
case DanmakuMsgType.GuardBuy:
if (this.config.RecordDanmakuGuard)
{
await this.xmlWriter.WriteStartElementAsync(null, "guard", null).ConfigureAwait(false);
var ts = Math.Max((DateTimeOffset.UtcNow - this.offset).TotalSeconds, 0d);
await this.xmlWriter.WriteAttributeStringAsync(null, "ts", null, ts.ToString("F3")).ConfigureAwait(false);
await this.xmlWriter.WriteAttributeStringAsync(null, "user", null, RemoveInvalidXMLChars(danmakuModel.UserName)).ConfigureAwait(false);
await this.xmlWriter.WriteAttributeStringAsync(null, "level", null, danmakuModel.UserGuardLevel.ToString()).ConfigureAwait(false); ;
await this.xmlWriter.WriteAttributeStringAsync(null, "count", null, danmakuModel.GiftCount.ToString()).ConfigureAwait(false);
if (recordDanmakuRaw)
await this.xmlWriter.WriteAttributeStringAsync(null, "raw", null, RemoveInvalidXMLChars(danmakuModel.RawObject?["data"]?.ToString(Newtonsoft.Json.Formatting.None))).ConfigureAwait(false);
await this.xmlWriter.WriteEndElementAsync().ConfigureAwait(false);
}
break;
default:
write = false;
break;
}
await this.xmlWriter.WriteStartElementAsync(null, "d", null).ConfigureAwait(false);
await this.xmlWriter.WriteAttributeStringAsync(null, "p", null, $"{ts:F3},{type},{size},{color},{st},0,{danmakuModel.UserID},0").ConfigureAwait(false);
await this.xmlWriter.WriteAttributeStringAsync(null, "user", null, RemoveInvalidXMLChars(danmakuModel.UserName)).ConfigureAwait(false);
if (recordDanmakuRaw)
await this.xmlWriter.WriteAttributeStringAsync(null, "raw", null, RemoveInvalidXMLChars(danmakuModel.RawObject?["info"]?.ToString(Newtonsoft.Json.Formatting.None))).ConfigureAwait(false);
this.xmlWriter.WriteValue(RemoveInvalidXMLChars(danmakuModel.CommentText));
await this.xmlWriter.WriteEndElementAsync().ConfigureAwait(false);
}
break;
case DanmakuMsgType.SuperChat:
if (this.config.RecordDanmakuSuperChat)
{
await this.xmlWriter.WriteStartElementAsync(null, "sc", null).ConfigureAwait(false);
var ts = Math.Max((DateTimeOffset.UtcNow - this.offset).TotalSeconds, 0d);
await this.xmlWriter.WriteAttributeStringAsync(null, "ts", null, ts.ToString("F3")).ConfigureAwait(false);
await this.xmlWriter.WriteAttributeStringAsync(null, "user", null, RemoveInvalidXMLChars(danmakuModel.UserName)).ConfigureAwait(false);
await this.xmlWriter.WriteAttributeStringAsync(null, "price", null, danmakuModel.Price.ToString()).ConfigureAwait(false);
await this.xmlWriter.WriteAttributeStringAsync(null, "time", null, danmakuModel.SCKeepTime.ToString()).ConfigureAwait(false);
if (recordDanmakuRaw)
await this.xmlWriter.WriteAttributeStringAsync(null, "raw", null, RemoveInvalidXMLChars(danmakuModel.RawObject?["data"]?.ToString(Newtonsoft.Json.Formatting.None))).ConfigureAwait(false);
this.xmlWriter.WriteValue(RemoveInvalidXMLChars(danmakuModel.CommentText));
await this.xmlWriter.WriteEndElementAsync().ConfigureAwait(false);
}
break;
case DanmakuMsgType.GiftSend:
if (this.config.RecordDanmakuGift)
{
await this.xmlWriter.WriteStartElementAsync(null, "gift", null).ConfigureAwait(false);
var ts = Math.Max((DateTimeOffset.UtcNow - this.offset).TotalSeconds, 0d);
await this.xmlWriter.WriteAttributeStringAsync(null, "ts", null, ts.ToString("F3")).ConfigureAwait(false);
await this.xmlWriter.WriteAttributeStringAsync(null, "user", null, RemoveInvalidXMLChars(danmakuModel.UserName)).ConfigureAwait(false);
await this.xmlWriter.WriteAttributeStringAsync(null, "giftname", null, RemoveInvalidXMLChars(danmakuModel.GiftName)).ConfigureAwait(false);
await this.xmlWriter.WriteAttributeStringAsync(null, "giftcount", null, danmakuModel.GiftCount.ToString()).ConfigureAwait(false);
if (recordDanmakuRaw)
await this.xmlWriter.WriteAttributeStringAsync(null, "raw", null, RemoveInvalidXMLChars(danmakuModel.RawObject?["data"]?.ToString(Newtonsoft.Json.Formatting.None))).ConfigureAwait(false);
await this.xmlWriter.WriteEndElementAsync().ConfigureAwait(false);
}
break;
case DanmakuMsgType.GuardBuy:
if (this.config.RecordDanmakuGuard)
{
await this.xmlWriter.WriteStartElementAsync(null, "guard", null).ConfigureAwait(false);
var ts = Math.Max((DateTimeOffset.UtcNow - this.offset).TotalSeconds, 0d);
await this.xmlWriter.WriteAttributeStringAsync(null, "ts", null, ts.ToString("F3")).ConfigureAwait(false);
await this.xmlWriter.WriteAttributeStringAsync(null, "user", null, RemoveInvalidXMLChars(danmakuModel.UserName)).ConfigureAwait(false);
await this.xmlWriter.WriteAttributeStringAsync(null, "level", null, danmakuModel.UserGuardLevel.ToString()).ConfigureAwait(false); ;
await this.xmlWriter.WriteAttributeStringAsync(null, "count", null, danmakuModel.GiftCount.ToString()).ConfigureAwait(false);
if (recordDanmakuRaw)
await this.xmlWriter.WriteAttributeStringAsync(null, "raw", null, RemoveInvalidXMLChars(danmakuModel.RawObject?["data"]?.ToString(Newtonsoft.Json.Formatting.None))).ConfigureAwait(false);
await this.xmlWriter.WriteEndElementAsync().ConfigureAwait(false);
}
break;
default:
write = false;
break;
}
if (write && this.writeCount++ >= this.config.RecordDanmakuFlushInterval)
{
await this.xmlWriter.FlushAsync();
this.writeCount = 0;
}
if (write && this.writeCount++ >= this.config.RecordDanmakuFlushInterval)
{
await this.xmlWriter.FlushAsync();
this.writeCount = 0;
}
}
catch (Exception ex)

View File

@ -481,7 +481,7 @@ namespace BililiveRecorder.Core
break;
}
_ = this.basicDanmakuWriter.WriteAsync(d);
_ = Task.Run(async () => await this.basicDanmakuWriter.WriteAsync(d));
}
private void DanmakuClient_StatusChanged(object sender, Api.Danmaku.StatusChangedEventArgs e)

View File

@ -135,6 +135,32 @@ globalThis.recorderEvents = {};
}
}
/// <summary>
/// 过滤保存的弹幕
/// </summary>
/// <param name="logger"></param>
/// <param name="json">弹幕 JSON 文本</param>
/// <returns>是否保存弹幕</returns>
public bool CallOnDanmaku(ILogger logger, string json)
{
const string callbackName = "onDanmaku";
var log = BuildLogger(logger);
try
{
var func = this.ExecuteScriptThenGetEventHandler(log, callbackName);
if (func is null) return true;
var result = func.Engine.Call(func, json);
return result.IsLooselyEqual(true);
}
catch (Exception ex)
{
log.Error(ex, $"执行脚本 {callbackName} 时发生错误");
return true;
}
}
/// <summary>
/// 获取直播流 URL
/// </summary>