mirror of
https://github.com/BililiveRecorder/BililiveRecorder.git
synced 2024-11-16 11:42:22 +08:00
Core: progress on scripting
This commit is contained in:
parent
43d1c6f2ef
commit
cc27045fc4
|
@ -37,7 +37,8 @@ namespace BililiveRecorder.Cli.Configure
|
|||
TimingWatchdogTimeout,
|
||||
RecordDanmakuFlushInterval,
|
||||
NetworkTransportUseSystemProxy,
|
||||
NetworkTransportAllowedAddressFamily
|
||||
NetworkTransportAllowedAddressFamily,
|
||||
UserScript
|
||||
}
|
||||
public enum RoomConfigProperties
|
||||
{
|
||||
|
@ -86,6 +87,7 @@ namespace BililiveRecorder.Cli.Configure
|
|||
GlobalConfig.Add(GlobalConfigProperties.RecordDanmakuFlushInterval, new ConfigInstruction<GlobalConfig, uint>(config => config.HasRecordDanmakuFlushInterval = false, (config, value) => config.RecordDanmakuFlushInterval = value) { Name = "RecordDanmakuFlushInterval", CanBeOptional = true });
|
||||
GlobalConfig.Add(GlobalConfigProperties.NetworkTransportUseSystemProxy, new ConfigInstruction<GlobalConfig, bool>(config => config.HasNetworkTransportUseSystemProxy = false, (config, value) => config.NetworkTransportUseSystemProxy = value) { Name = "NetworkTransportUseSystemProxy", CanBeOptional = true });
|
||||
GlobalConfig.Add(GlobalConfigProperties.NetworkTransportAllowedAddressFamily, new ConfigInstruction<GlobalConfig, AllowedAddressFamily>(config => config.HasNetworkTransportAllowedAddressFamily = false, (config, value) => config.NetworkTransportAllowedAddressFamily = value) { Name = "NetworkTransportAllowedAddressFamily", CanBeOptional = true });
|
||||
GlobalConfig.Add(GlobalConfigProperties.UserScript, new ConfigInstruction<GlobalConfig, string>(config => config.HasUserScript = false, (config, value) => config.UserScript = value) { Name = "UserScript", CanBeOptional = true });
|
||||
|
||||
RoomConfig.Add(RoomConfigProperties.RoomId, new ConfigInstruction<RoomConfig, int>(config => config.HasRoomId = false, (config, value) => config.RoomId = value) { Name = "RoomId", CanBeOptional = false });
|
||||
RoomConfig.Add(RoomConfigProperties.AutoRecord, new ConfigInstruction<RoomConfig, bool>(config => config.HasAutoRecord = false, (config, value) => config.AutoRecord = value) { Name = "AutoRecord", CanBeOptional = false });
|
||||
|
|
|
@ -5,10 +5,15 @@
|
|||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<DebugType>portable</DebugType>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<RestoreAdditionalProjectSources>
|
||||
https://api.nuget.org/v3/index.json;
|
||||
https://www.myget.org/F/jint/api/v3/index.json
|
||||
</RestoreAdditionalProjectSources>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Fluid.Core" Version="2.2.8" />
|
||||
<PackageReference Include="Jint" Version="3.0.0-preview-275" />
|
||||
<PackageReference Include="JsonSubTypes" Version="1.8.0" />
|
||||
<PackageReference Include="HierarchicalPropertyDefault" Version="0.1.4-beta-g75fdf624b1" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="6.0.0" />
|
||||
|
|
|
@ -176,6 +176,11 @@ namespace BililiveRecorder.Core.Config.V3
|
|||
/// </summary>
|
||||
public AllowedAddressFamily NetworkTransportAllowedAddressFamily => this.GetPropertyValue<AllowedAddressFamily>();
|
||||
|
||||
/// <summary>
|
||||
/// 自定义脚本
|
||||
/// </summary>
|
||||
public string UserScript => this.GetPropertyValue<string>();
|
||||
|
||||
}
|
||||
|
||||
[JsonObject(MemberSerialization.OptIn)]
|
||||
|
@ -373,6 +378,14 @@ namespace BililiveRecorder.Core.Config.V3
|
|||
[JsonProperty(nameof(NetworkTransportAllowedAddressFamily)), EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public Optional<AllowedAddressFamily> OptionalNetworkTransportAllowedAddressFamily { get => this.GetPropertyValueOptional<AllowedAddressFamily>(nameof(this.NetworkTransportAllowedAddressFamily)); set => this.SetPropertyValueOptional(value, nameof(this.NetworkTransportAllowedAddressFamily)); }
|
||||
|
||||
/// <summary>
|
||||
/// 自定义脚本
|
||||
/// </summary>
|
||||
public string UserScript { get => this.GetPropertyValue<string>(); set => this.SetPropertyValue(value); }
|
||||
public bool HasUserScript { get => this.GetPropertyHasValue(nameof(this.UserScript)); set => this.SetPropertyHasValue<string>(value, nameof(this.UserScript)); }
|
||||
[JsonProperty(nameof(UserScript)), EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public Optional<string> OptionalUserScript { get => this.GetPropertyValueOptional<string>(nameof(this.UserScript)); set => this.SetPropertyValueOptional(value, nameof(this.UserScript)); }
|
||||
|
||||
}
|
||||
|
||||
public sealed partial class DefaultConfig
|
||||
|
@ -428,6 +441,8 @@ namespace BililiveRecorder.Core.Config.V3
|
|||
|
||||
public AllowedAddressFamily NetworkTransportAllowedAddressFamily => AllowedAddressFamily.Any;
|
||||
|
||||
public string UserScript => string.Empty;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
208
BililiveRecorder.Core/Scripting/Runtime/JintConsole.cs
Normal file
208
BililiveRecorder.Core/Scripting/Runtime/JintConsole.cs
Normal file
|
@ -0,0 +1,208 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Jint;
|
||||
using Jint.Native;
|
||||
using Jint.Native.Json;
|
||||
using Jint.Native.Object;
|
||||
using Jint.Runtime;
|
||||
using Jint.Runtime.Interop;
|
||||
using Serilog;
|
||||
|
||||
namespace BililiveRecorder.Core.Scripting.Runtime
|
||||
{
|
||||
public class JintConsole : ObjectInstance
|
||||
{
|
||||
private readonly ILogger logger;
|
||||
|
||||
private readonly Dictionary<string, int> counters = new();
|
||||
private readonly Dictionary<string, Stopwatch> timers = new();
|
||||
|
||||
public JintConsole(Engine engine, ILogger logger) : base(engine)
|
||||
{
|
||||
this.logger = logger?.ForContext<JintConsole>() ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
protected override void Initialize()
|
||||
{
|
||||
Add("assert", this.Assert);
|
||||
Add("clear", this.Clear);
|
||||
Add("count", this.Count);
|
||||
Add("countReset", this.CountReset);
|
||||
Add("debug", this.Debug);
|
||||
Add("error", this.Error);
|
||||
Add("info", this.Info);
|
||||
Add("log", this.Log);
|
||||
Add("time", this.Time);
|
||||
Add("timeEnd", this.TimeEnd);
|
||||
Add("timeLog", this.TimeLog);
|
||||
Add("trace", this.Trace);
|
||||
Add("warn", this.Warn);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
void Add(string name, Func<JsValue, JsValue[], JsValue> func)
|
||||
{
|
||||
this.FastAddProperty(name, new ClrFunctionInstance(this._engine, name, func), false, false, false);
|
||||
}
|
||||
}
|
||||
|
||||
private string[] FormatToString(ReadOnlySpan<JsValue> values)
|
||||
{
|
||||
var result = new string[values.Length];
|
||||
var jsonSerializer = new Lazy<JsonSerializer>(() => new JsonSerializer(this._engine));
|
||||
|
||||
for (var i = 0; i < values.Length; i++)
|
||||
{
|
||||
var value = values[i];
|
||||
var text = value switch
|
||||
{
|
||||
JsString jsString => jsString.ToString(),
|
||||
JsBoolean or JsNumber or JsBigInt or JsNull or JsUndefined => value.ToString(),
|
||||
_ => jsonSerializer.Value.Serialize(values[i], Undefined, Undefined).ToString()
|
||||
};
|
||||
result[i] = text;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// TODO: Add call stack support
|
||||
// Workaround: use `new Error().stack` in js side
|
||||
// ref: https://github.com/sebastienros/jint/discussions/1115
|
||||
|
||||
private JsValue Assert(JsValue thisObject, JsValue[] arguments)
|
||||
{
|
||||
if (arguments.At(0).IsLooselyEqual(0))
|
||||
{
|
||||
string[] messages;
|
||||
|
||||
if (arguments.Length < 2)
|
||||
{
|
||||
messages = Array.Empty<string>();
|
||||
}
|
||||
else
|
||||
{
|
||||
messages = this.FormatToString(arguments.AsSpan(1));
|
||||
}
|
||||
|
||||
this.logger.Error("[Script] Assertion failed: {Messages}", messages);
|
||||
}
|
||||
return Undefined;
|
||||
}
|
||||
|
||||
private JsValue Clear(JsValue thisObject, JsValue[] arguments) => Undefined; // noop
|
||||
|
||||
private JsValue Count(JsValue thisObject, JsValue[] arguments)
|
||||
{
|
||||
var name = arguments.Length > 0 ? arguments[0].ToString() : "default";
|
||||
if (this.counters.TryGetValue(name, out var count))
|
||||
{
|
||||
count++;
|
||||
}
|
||||
else
|
||||
{
|
||||
count = 1;
|
||||
}
|
||||
this.counters[name] = count;
|
||||
|
||||
this.logger.Information("[Script] {CounterName}: {Count}", name, count);
|
||||
|
||||
return Undefined;
|
||||
}
|
||||
|
||||
private JsValue CountReset(JsValue thisObject, JsValue[] arguments)
|
||||
{
|
||||
var name = arguments.Length > 0 ? arguments[0].ToString() : "default";
|
||||
this.counters.Remove(name);
|
||||
this.logger.Information("[Script] {CounterName}: {Count}", name, 0);
|
||||
return Undefined;
|
||||
}
|
||||
|
||||
private JsValue Debug(JsValue thisObject, JsValue[] arguments)
|
||||
{
|
||||
var messages = this.FormatToString(arguments);
|
||||
this.logger.Debug("[Script] {Messages}", messages);
|
||||
return Undefined;
|
||||
}
|
||||
|
||||
private JsValue Error(JsValue thisObject, JsValue[] arguments)
|
||||
{
|
||||
var messages = this.FormatToString(arguments);
|
||||
this.logger.Error("[Script] {Messages}", messages);
|
||||
return Undefined;
|
||||
}
|
||||
|
||||
private JsValue Info(JsValue thisObject, JsValue[] arguments)
|
||||
{
|
||||
var messages = this.FormatToString(arguments);
|
||||
this.logger.Information("[Script] {Messages}", messages);
|
||||
return Undefined;
|
||||
}
|
||||
|
||||
private JsValue Log(JsValue thisObject, JsValue[] arguments)
|
||||
{
|
||||
var messages = this.FormatToString(arguments);
|
||||
this.logger.Information("[Script] {Messages}", messages);
|
||||
return Undefined;
|
||||
}
|
||||
|
||||
private JsValue Time(JsValue thisObject, JsValue[] arguments)
|
||||
{
|
||||
var name = arguments.Length > 0 ? arguments[0].ToString() : "default";
|
||||
if (this.timers.ContainsKey(name))
|
||||
{
|
||||
this.logger.Warning("[Script] Timer {TimerName} already exists", name);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.timers[name] = Stopwatch.StartNew();
|
||||
}
|
||||
return Undefined;
|
||||
}
|
||||
|
||||
private JsValue TimeEnd(JsValue thisObject, JsValue[] arguments)
|
||||
{
|
||||
var name = arguments.Length > 0 ? arguments[0].ToString() : "default";
|
||||
if (this.timers.TryGetValue(name, out var timer))
|
||||
{
|
||||
timer.Stop();
|
||||
this.timers.Remove(name);
|
||||
this.logger.Information("[Script] {TimerName}: {ElapsedMilliseconds} ms", name, timer.ElapsedMilliseconds);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.logger.Warning("[Script] Timer {TimerName} does not exist", name);
|
||||
}
|
||||
return Undefined;
|
||||
}
|
||||
|
||||
private JsValue TimeLog(JsValue thisObject, JsValue[] arguments)
|
||||
{
|
||||
var name = arguments.Length > 0 ? arguments[0].ToString() : "default";
|
||||
if (this.timers.TryGetValue(name, out var timer))
|
||||
{
|
||||
this.logger.Information("[Script] {TimerName}: {ElapsedMilliseconds} ms", name, timer.ElapsedMilliseconds);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.logger.Warning("[Script] Timer {TimerName} does not exist", name);
|
||||
}
|
||||
return Undefined;
|
||||
}
|
||||
|
||||
private JsValue Trace(JsValue thisObject, JsValue[] arguments)
|
||||
{
|
||||
var messages = this.FormatToString(arguments);
|
||||
this.logger.Information("[Script] {Messages}", messages);
|
||||
return Undefined;
|
||||
}
|
||||
|
||||
private JsValue Warn(JsValue thisObject, JsValue[] arguments)
|
||||
{
|
||||
var messages = this.FormatToString(arguments);
|
||||
this.logger.Warning("[Script] {Messages}", messages);
|
||||
return Undefined;
|
||||
}
|
||||
}
|
||||
}
|
12
BililiveRecorder.Core/Scripting/Runtime/JintDns.cs
Normal file
12
BililiveRecorder.Core/Scripting/Runtime/JintDns.cs
Normal file
|
@ -0,0 +1,12 @@
|
|||
using Jint;
|
||||
using Jint.Native.Object;
|
||||
|
||||
namespace BililiveRecorder.Core.Scripting.Runtime
|
||||
{
|
||||
public class JintDns : ObjectInstance
|
||||
{
|
||||
public JintDns(Engine engine) : base(engine)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
12
BililiveRecorder.Core/Scripting/Runtime/JintDotnet.cs
Normal file
12
BililiveRecorder.Core/Scripting/Runtime/JintDotnet.cs
Normal file
|
@ -0,0 +1,12 @@
|
|||
using Jint;
|
||||
using Jint.Native.Object;
|
||||
|
||||
namespace BililiveRecorder.Core.Scripting.Runtime
|
||||
{
|
||||
public class JintDotnet : ObjectInstance
|
||||
{
|
||||
public JintDotnet(Engine engine) : base(engine)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
20
BililiveRecorder.Core/Scripting/Runtime/JintFetchSync.cs
Normal file
20
BililiveRecorder.Core/Scripting/Runtime/JintFetchSync.cs
Normal file
|
@ -0,0 +1,20 @@
|
|||
using Jint;
|
||||
using Jint.Native;
|
||||
using Jint.Native.Function;
|
||||
|
||||
namespace BililiveRecorder.Core.Scripting.Runtime
|
||||
{
|
||||
public class JintFetchSync : FunctionInstance
|
||||
{
|
||||
private static readonly JsString functionName = new JsString("fetch");
|
||||
|
||||
public JintFetchSync(Engine engine) : base(engine, engine.Realm, functionName)
|
||||
{
|
||||
}
|
||||
|
||||
protected override JsValue Call(JsValue thisObject, JsValue[] arguments)
|
||||
{
|
||||
return Undefined;
|
||||
}
|
||||
}
|
||||
}
|
143
BililiveRecorder.Core/Scripting/UserScriptRunner.cs
Normal file
143
BililiveRecorder.Core/Scripting/UserScriptRunner.cs
Normal file
|
@ -0,0 +1,143 @@
|
|||
using System;
|
||||
using System.Threading;
|
||||
using BililiveRecorder.Core.Config.V3;
|
||||
using BililiveRecorder.Core.Scripting.Runtime;
|
||||
using Esprima;
|
||||
using Esprima.Ast;
|
||||
using Jint;
|
||||
using Jint.Native.Function;
|
||||
using Jint.Native.Object;
|
||||
using Serilog;
|
||||
|
||||
namespace BililiveRecorder.Core.Scripting
|
||||
{
|
||||
public class UserScriptRunner
|
||||
{
|
||||
private static int ExecutionId = 0;
|
||||
|
||||
private readonly GlobalConfig config;
|
||||
private readonly Options jintOptions;
|
||||
|
||||
private static readonly Script setupScript;
|
||||
|
||||
private string? cachedScriptSource;
|
||||
private Script? cachedScript;
|
||||
|
||||
static UserScriptRunner()
|
||||
{
|
||||
setupScript = new JavaScriptParser(@"
|
||||
globalThis.recorderEvents = {};
|
||||
").ParseScript();
|
||||
}
|
||||
|
||||
public UserScriptRunner(GlobalConfig config)
|
||||
{
|
||||
this.config = config ?? throw new ArgumentNullException(nameof(config));
|
||||
|
||||
this.jintOptions = new Options()
|
||||
.CatchClrExceptions()
|
||||
.LimitRecursion(100)
|
||||
.RegexTimeoutInterval(TimeSpan.FromSeconds(2))
|
||||
.Configure(engine =>
|
||||
{
|
||||
engine.Realm.GlobalObject.FastAddProperty("dns", new JintDns(engine), writable: false, enumerable: false, configurable: false);
|
||||
engine.Realm.GlobalObject.FastAddProperty("dotnet", new JintDotnet(engine), writable: false, enumerable: false, configurable: false);
|
||||
engine.Realm.GlobalObject.FastAddProperty("fetchSync", new JintFetchSync(engine), writable: false, enumerable: false, configurable: false);
|
||||
});
|
||||
}
|
||||
|
||||
private Script? GetParsedScript()
|
||||
{
|
||||
var source = this.config.UserScript;
|
||||
|
||||
if (this.cachedScript is not null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(source))
|
||||
{
|
||||
this.cachedScript = null;
|
||||
this.cachedScriptSource = null;
|
||||
return null;
|
||||
}
|
||||
else if (this.cachedScriptSource == source)
|
||||
{
|
||||
return this.cachedScript;
|
||||
}
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(source))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var parser = new JavaScriptParser(source);
|
||||
var script = parser.ParseScript();
|
||||
|
||||
this.cachedScript = script;
|
||||
this.cachedScriptSource = source;
|
||||
|
||||
return script;
|
||||
}
|
||||
|
||||
private Engine CreateJintEngine(ILogger logger)
|
||||
{
|
||||
var engine = new Engine(this.jintOptions);
|
||||
|
||||
engine.Realm.GlobalObject.FastAddProperty("console", new JintConsole(engine, logger), writable: false, enumerable: false, configurable: false);
|
||||
|
||||
engine.Execute(setupScript);
|
||||
|
||||
return engine;
|
||||
}
|
||||
|
||||
public string CallOnStreamUrl(ILogger logger, string originalUrl)
|
||||
{
|
||||
var log = BuildLogger(logger);
|
||||
|
||||
var script = this.GetParsedScript();
|
||||
|
||||
if (script is null)
|
||||
{
|
||||
return originalUrl;
|
||||
}
|
||||
|
||||
var engine = this.CreateJintEngine(log);
|
||||
|
||||
engine.Execute(script);
|
||||
|
||||
if (engine.Realm.GlobalObject.Get("recorderEvents") is not ObjectInstance events)
|
||||
{
|
||||
log.Warning("脚本: recorderEvents 被修改为非 object");
|
||||
return originalUrl;
|
||||
}
|
||||
|
||||
if (events.Get("onStreamUrl") is not FunctionInstance func)
|
||||
{
|
||||
return originalUrl;
|
||||
}
|
||||
|
||||
var result = engine.Call(func, originalUrl);
|
||||
|
||||
if (result.Type == Jint.Runtime.Types.String)
|
||||
{
|
||||
|
||||
|
||||
}
|
||||
else if (result.Type == Jint.Runtime.Types.Object)
|
||||
{
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
private static ILogger BuildLogger(ILogger logger)
|
||||
{
|
||||
var id = Interlocked.Increment(ref ExecutionId);
|
||||
return logger.ForContext<UserScriptRunner>().ForContext(nameof(ExecutionId), id);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -12,6 +12,7 @@ using System.Threading.Tasks;
|
|||
using System.Windows.Threading;
|
||||
using BililiveRecorder.ToolBox;
|
||||
using Sentry;
|
||||
using Sentry.Extensibility;
|
||||
using Serilog;
|
||||
using Serilog.Core;
|
||||
using Serilog.Exceptions;
|
||||
|
@ -238,6 +239,8 @@ namespace BililiveRecorder.WPF
|
|||
o.DisableAppDomainUnhandledExceptionCapture();
|
||||
o.DisableTaskUnobservedTaskExceptionCapture();
|
||||
o.AddExceptionFilterForType<System.Net.Http.HttpRequestException>();
|
||||
o.AddExceptionFilterForType<OutOfMemoryException>();
|
||||
o.AddEventProcessor(new SentryEventProcessor());
|
||||
|
||||
o.TextFormatter = new MessageTemplateTextFormatter("[{RoomId}] {Message}{NewLine}{Exception}{@ExceptionDetail:j}");
|
||||
|
||||
|
@ -269,5 +272,11 @@ namespace BililiveRecorder.WPF
|
|||
[HandleProcessCorruptedStateExceptions, SecurityCritical]
|
||||
private static void App_DispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e) =>
|
||||
logger.Fatal(e.Exception, "Unhandled exception from Application.DispatcherUnhandledException");
|
||||
|
||||
private class SentryEventProcessor : ISentryEventProcessor
|
||||
{
|
||||
private static readonly string NameOfJintConsole = typeof(Core.Scripting.Runtime.JintConsole).FullName;
|
||||
public SentryEvent? Process(SentryEvent e) => e?.Logger == NameOfJintConsole ? null : e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -64,6 +64,7 @@ namespace BililiveRecorder.Web.Models
|
|||
public Optional<uint>? OptionalRecordDanmakuFlushInterval { get; set; }
|
||||
public Optional<bool>? OptionalNetworkTransportUseSystemProxy { get; set; }
|
||||
public Optional<AllowedAddressFamily>? OptionalNetworkTransportAllowedAddressFamily { get; set; }
|
||||
public Optional<string>? OptionalUserScript { get; set; }
|
||||
|
||||
public void ApplyTo(GlobalConfig config)
|
||||
{
|
||||
|
@ -91,6 +92,7 @@ namespace BililiveRecorder.Web.Models
|
|||
if (this.OptionalRecordDanmakuFlushInterval.HasValue) config.OptionalRecordDanmakuFlushInterval = this.OptionalRecordDanmakuFlushInterval.Value;
|
||||
if (this.OptionalNetworkTransportUseSystemProxy.HasValue) config.OptionalNetworkTransportUseSystemProxy = this.OptionalNetworkTransportUseSystemProxy.Value;
|
||||
if (this.OptionalNetworkTransportAllowedAddressFamily.HasValue) config.OptionalNetworkTransportAllowedAddressFamily = this.OptionalNetworkTransportAllowedAddressFamily.Value;
|
||||
if (this.OptionalUserScript.HasValue) config.OptionalUserScript = this.OptionalUserScript.Value;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -138,6 +140,7 @@ namespace BililiveRecorder.Web.Models.Rest
|
|||
public Optional<uint> OptionalRecordDanmakuFlushInterval { get; set; }
|
||||
public Optional<bool> OptionalNetworkTransportUseSystemProxy { get; set; }
|
||||
public Optional<AllowedAddressFamily> OptionalNetworkTransportAllowedAddressFamily { get; set; }
|
||||
public Optional<string> OptionalUserScript { get; set; }
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -190,6 +193,7 @@ namespace BililiveRecorder.Web.Models.Graphql
|
|||
this.Field(x => x.OptionalRecordDanmakuFlushInterval, type: typeof(HierarchicalOptionalType<uint>));
|
||||
this.Field(x => x.OptionalNetworkTransportUseSystemProxy, type: typeof(HierarchicalOptionalType<bool>));
|
||||
this.Field(x => x.OptionalNetworkTransportAllowedAddressFamily, type: typeof(HierarchicalOptionalType<AllowedAddressFamily>));
|
||||
this.Field(x => x.OptionalUserScript, type: typeof(HierarchicalOptionalType<string>));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -221,6 +225,7 @@ namespace BililiveRecorder.Web.Models.Graphql
|
|||
this.Field(x => x.RecordDanmakuFlushInterval);
|
||||
this.Field(x => x.NetworkTransportUseSystemProxy);
|
||||
this.Field(x => x.NetworkTransportAllowedAddressFamily);
|
||||
this.Field(x => x.UserScript);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -269,6 +274,7 @@ namespace BililiveRecorder.Web.Models.Graphql
|
|||
this.Field(x => x.OptionalRecordDanmakuFlushInterval, nullable: true, type: typeof(HierarchicalOptionalInputType<uint>));
|
||||
this.Field(x => x.OptionalNetworkTransportUseSystemProxy, nullable: true, type: typeof(HierarchicalOptionalInputType<bool>));
|
||||
this.Field(x => x.OptionalNetworkTransportAllowedAddressFamily, nullable: true, type: typeof(HierarchicalOptionalInputType<AllowedAddressFamily>));
|
||||
this.Field(x => x.OptionalUserScript, nullable: true, type: typeof(HierarchicalOptionalInputType<string>));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -261,6 +261,22 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"UserScript": {
|
||||
"description": "自定义脚本\n默认: ",
|
||||
"markdownDescription": "自定义脚本 \n默认: ` `\n\n",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"HasValue": {
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
"Value": {
|
||||
"type": "string",
|
||||
"default": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"RecordMode": {
|
||||
"description": "录制模式\n默认: RecordMode.Standard",
|
||||
"markdownDescription": "录制模式 \n默认: `RecordMode.Standard `\n\n本设置项是一个 enum,键值对应如下:\n\n| 键 | 值 |\n|:--:|:--:|\n| RecordMode.Standard | 0 |\n| RecordMode.RawData | 1 |\n\n关于录制模式的说明见 [录制模式](/docs/basic/record_mode/)",
|
||||
|
|
|
@ -234,4 +234,13 @@ export const data: Array<ConfigEntry> = [
|
|||
advancedConfig: true,
|
||||
markdown: ""
|
||||
},
|
||||
{
|
||||
name: "UserScript",
|
||||
description: "自定义脚本",
|
||||
type: "string",
|
||||
defaultValue: "string.Empty",
|
||||
configType: "globalOnly",
|
||||
advancedConfig: true,
|
||||
markdown: ""
|
||||
},
|
||||
];
|
||||
|
|
Loading…
Reference in New Issue
Block a user