mirror of
https://github.com/BililiveRecorder/BililiveRecorder.git
synced 2024-11-15 19:22:19 +08:00
Add Web API
This commit is contained in:
parent
30a659f4d2
commit
db7f2872f8
|
@ -4,6 +4,7 @@
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<TargetFramework>net6.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
<StartupObject>BililiveRecorder.Cli.Program</StartupObject>
|
<StartupObject>BililiveRecorder.Cli.Program</StartupObject>
|
||||||
|
<ValidateExecutableReferencesMatchSelfContained>false</ValidateExecutableReferencesMatchSelfContained>
|
||||||
<RuntimeIdentifiers>win-x64;osx-x64;osx.10.11-x64;linux-arm64;linux-arm;linux-x64</RuntimeIdentifiers>
|
<RuntimeIdentifiers>win-x64;osx-x64;osx.10.11-x64;linux-arm64;linux-arm;linux-x64</RuntimeIdentifiers>
|
||||||
<RuntimeIdentifier Condition=" '$(RuntimeIdentifier)' == 'any' "></RuntimeIdentifier>
|
<RuntimeIdentifier Condition=" '$(RuntimeIdentifier)' == 'any' "></RuntimeIdentifier>
|
||||||
<PublishDir Condition=" '$(RuntimeIdentifier)' == '' ">publish\any</PublishDir>
|
<PublishDir Condition=" '$(RuntimeIdentifier)' == '' ">publish\any</PublishDir>
|
||||||
|
@ -22,6 +23,7 @@
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="6.0.0" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="6.0.0" />
|
||||||
<PackageReference Include="Serilog" Version="2.10.0" />
|
<PackageReference Include="Serilog" Version="2.10.0" />
|
||||||
|
<PackageReference Include="Serilog.AspNetCore" Version="4.1.0" />
|
||||||
<PackageReference Include="Serilog.Enrichers.Process" Version="2.0.2" />
|
<PackageReference Include="Serilog.Enrichers.Process" Version="2.0.2" />
|
||||||
<PackageReference Include="Serilog.Enrichers.Thread" Version="3.1.0" />
|
<PackageReference Include="Serilog.Enrichers.Thread" Version="3.1.0" />
|
||||||
<PackageReference Include="Serilog.Exceptions" Version="8.0.0" />
|
<PackageReference Include="Serilog.Exceptions" Version="8.0.0" />
|
||||||
|
@ -35,6 +37,7 @@
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\BililiveRecorder.Core\BililiveRecorder.Core.csproj" />
|
<ProjectReference Include="..\BililiveRecorder.Core\BililiveRecorder.Core.csproj" />
|
||||||
<ProjectReference Include="..\BililiveRecorder.ToolBox\BililiveRecorder.ToolBox.csproj" />
|
<ProjectReference Include="..\BililiveRecorder.ToolBox\BililiveRecorder.ToolBox.csproj" />
|
||||||
|
<ProjectReference Include="..\BililiveRecorder.Web\BililiveRecorder.Web.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
@ -6,12 +6,16 @@ using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using BililiveRecorder.Cli.Configure;
|
using BililiveRecorder.Cli.Configure;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using BililiveRecorder.Core;
|
using BililiveRecorder.Core;
|
||||||
using BililiveRecorder.Core.Config;
|
using BililiveRecorder.Core.Config;
|
||||||
using BililiveRecorder.Core.Config.V2;
|
using BililiveRecorder.Core.Config.V2;
|
||||||
using BililiveRecorder.DependencyInjection;
|
using BililiveRecorder.DependencyInjection;
|
||||||
using BililiveRecorder.ToolBox;
|
using BililiveRecorder.ToolBox;
|
||||||
|
using BililiveRecorder.Web;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
using Serilog.Core;
|
using Serilog.Core;
|
||||||
using Serilog.Events;
|
using Serilog.Events;
|
||||||
|
@ -26,12 +30,13 @@ namespace BililiveRecorder.Cli
|
||||||
{
|
{
|
||||||
var cmd_run = new Command("run", "Run BililiveRecorder in standard mode")
|
var cmd_run = new Command("run", "Run BililiveRecorder in standard mode")
|
||||||
{
|
{
|
||||||
|
new Option<string?>(new []{ "--web-bind", "--bind", "-b" }, () => null, "Bind address for web api"),
|
||||||
new Option<LogEventLevel>(new []{ "--loglevel", "--log", "-l" }, () => LogEventLevel.Information, "Minimal log level output to console"),
|
new Option<LogEventLevel>(new []{ "--loglevel", "--log", "-l" }, () => LogEventLevel.Information, "Minimal log level output to console"),
|
||||||
new Option<LogEventLevel>(new []{ "--logfilelevel", "--flog" }, () => LogEventLevel.Debug, "Minimal log level output to file"),
|
new Option<LogEventLevel>(new []{ "--logfilelevel", "--flog" }, () => LogEventLevel.Debug, "Minimal log level output to file"),
|
||||||
new Argument<string>("path"),
|
new Argument<string>("path"),
|
||||||
};
|
};
|
||||||
cmd_run.AddAlias("r");
|
cmd_run.AddAlias("r");
|
||||||
cmd_run.Handler = CommandHandler.Create<string, LogEventLevel, LogEventLevel>(RunConfigMode);
|
cmd_run.Handler = CommandHandler.Create<RunModeArguments>(RunConfigModeAsync);
|
||||||
|
|
||||||
var cmd_portable = new Command("portable", "Run BililiveRecorder in config-less mode")
|
var cmd_portable = new Command("portable", "Run BililiveRecorder in config-less mode")
|
||||||
{
|
{
|
||||||
|
@ -60,9 +65,11 @@ namespace BililiveRecorder.Cli
|
||||||
return root.Invoke(args);
|
return root.Invoke(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int RunConfigMode(string path, LogEventLevel logLevel, LogEventLevel logFileLevel)
|
private static async Task<int> RunConfigModeAsync(RunModeArguments args)
|
||||||
{
|
{
|
||||||
using var logger = BuildLogger(logLevel, logFileLevel);
|
var path = args.Path;
|
||||||
|
|
||||||
|
using var logger = BuildLogger(args.LogLevel, args.LogFileLevel);
|
||||||
Log.Logger = logger;
|
Log.Logger = logger;
|
||||||
|
|
||||||
path = Path.GetFullPath(path);
|
path = Path.GetFullPath(path);
|
||||||
|
@ -76,22 +83,64 @@ namespace BililiveRecorder.Cli
|
||||||
config.Global.WorkDirectory = path;
|
config.Global.WorkDirectory = path;
|
||||||
|
|
||||||
var serviceProvider = BuildServiceProvider(config, logger);
|
var serviceProvider = BuildServiceProvider(config, logger);
|
||||||
|
IRecorder recorderAccessProxy(IServiceProvider x) => serviceProvider.GetRequiredService<IRecorder>();
|
||||||
|
|
||||||
|
// recorder setup done
|
||||||
|
// check if web service required
|
||||||
|
IHost? host = null;
|
||||||
|
if (string.IsNullOrWhiteSpace(args.WebBind))
|
||||||
|
{
|
||||||
|
logger.Information("Web API not enabled");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
logger.Information("Creating web server on {BindAddress}", args.WebBind);
|
||||||
|
|
||||||
|
host = new HostBuilder()
|
||||||
|
.ConfigureServices(services =>
|
||||||
|
{
|
||||||
|
services.AddSingleton(recorderAccessProxy);
|
||||||
|
})
|
||||||
|
.ConfigureWebHost(webBuilder =>
|
||||||
|
{
|
||||||
|
webBuilder
|
||||||
|
.UseUrls(urls: args.WebBind)
|
||||||
|
.UseKestrel()
|
||||||
|
.UseSerilog(logger: logger)
|
||||||
|
.UseStartup<Startup>();
|
||||||
|
})
|
||||||
|
.Build();
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure recorder is created, also used in cleanup ( .Dispose() )
|
||||||
var recorder = serviceProvider.GetRequiredService<IRecorder>();
|
var recorder = serviceProvider.GetRequiredService<IRecorder>();
|
||||||
|
|
||||||
ConsoleCancelEventHandler p = null!;
|
ConsoleCancelEventHandler p = null!;
|
||||||
var semaphore = new SemaphoreSlim(0, 1);
|
var cts = new CancellationTokenSource();
|
||||||
p = (sender, e) =>
|
p = (sender, e) =>
|
||||||
{
|
{
|
||||||
|
logger.Information("Ctrl+C pressed. Exiting");
|
||||||
Console.CancelKeyPress -= p;
|
Console.CancelKeyPress -= p;
|
||||||
e.Cancel = true;
|
e.Cancel = true;
|
||||||
recorder.Dispose();
|
cts.Cancel();
|
||||||
semaphore.Release();
|
|
||||||
};
|
};
|
||||||
Console.CancelKeyPress += p;
|
Console.CancelKeyPress += p;
|
||||||
|
|
||||||
semaphore.Wait();
|
var token = cts.Token;
|
||||||
Thread.Sleep(1000 * 2);
|
if (host is not null)
|
||||||
Console.ReadLine();
|
{
|
||||||
|
var hostStartTask = host.RunAsync(token);
|
||||||
|
await Task.WhenAny(Task.Delay(-1, token), hostStartTask).ConfigureAwait(false);
|
||||||
|
await host.StopAsync().ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await Task.Delay(-1, token).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
recorder.Dispose();
|
||||||
|
|
||||||
|
await Task.Delay(1000 * 3).ConfigureAwait(false);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -140,6 +189,7 @@ namespace BililiveRecorder.Cli
|
||||||
var semaphore = new SemaphoreSlim(0, 1);
|
var semaphore = new SemaphoreSlim(0, 1);
|
||||||
p = (sender, e) =>
|
p = (sender, e) =>
|
||||||
{
|
{
|
||||||
|
logger.Information("Ctrl+C pressed. Exiting");
|
||||||
Console.CancelKeyPress -= p;
|
Console.CancelKeyPress -= p;
|
||||||
e.Cancel = true;
|
e.Cancel = true;
|
||||||
recorder.Dispose();
|
recorder.Dispose();
|
||||||
|
@ -179,6 +229,17 @@ namespace BililiveRecorder.Cli
|
||||||
.WriteTo.File(new CompactJsonFormatter(), "./logs/bilirec.txt", restrictedToMinimumLevel: logFileLevel, shared: true, rollingInterval: RollingInterval.Day, rollOnFileSizeLimit: true)
|
.WriteTo.File(new CompactJsonFormatter(), "./logs/bilirec.txt", restrictedToMinimumLevel: logFileLevel, shared: true, rollingInterval: RollingInterval.Day, rollOnFileSizeLimit: true)
|
||||||
.CreateLogger();
|
.CreateLogger();
|
||||||
|
|
||||||
|
public class RunModeArguments
|
||||||
|
{
|
||||||
|
public LogEventLevel LogLevel { get; set; } = LogEventLevel.Information;
|
||||||
|
|
||||||
|
public LogEventLevel LogFileLevel { get; set; } = LogEventLevel.Information;
|
||||||
|
|
||||||
|
public string? WebBind { get; set; } = null;
|
||||||
|
|
||||||
|
public string Path { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
public class PortableModeArguments
|
public class PortableModeArguments
|
||||||
{
|
{
|
||||||
public LogEventLevel LogLevel { get; set; } = LogEventLevel.Information;
|
public LogEventLevel LogLevel { get; set; } = LogEventLevel.Information;
|
||||||
|
|
|
@ -18,8 +18,8 @@ namespace BililiveRecorder.Core
|
||||||
event EventHandler<AggregatedRoomEventArgs<NetworkingStatsEventArgs>>? NetworkingStats;
|
event EventHandler<AggregatedRoomEventArgs<NetworkingStatsEventArgs>>? NetworkingStats;
|
||||||
event EventHandler<AggregatedRoomEventArgs<RecordingStatsEventArgs>>? RecordingStats;
|
event EventHandler<AggregatedRoomEventArgs<RecordingStatsEventArgs>>? RecordingStats;
|
||||||
|
|
||||||
void AddRoom(int roomid);
|
IRoom AddRoom(int roomid);
|
||||||
void AddRoom(int roomid, bool enabled);
|
IRoom AddRoom(int roomid, bool enabled);
|
||||||
void RemoveRoom(IRoom room);
|
void RemoveRoom(IRoom room);
|
||||||
|
|
||||||
void SaveConfig();
|
void SaveConfig();
|
||||||
|
|
|
@ -58,20 +58,21 @@ namespace BililiveRecorder.Core
|
||||||
|
|
||||||
public ReadOnlyObservableCollection<IRoom> Rooms { get; }
|
public ReadOnlyObservableCollection<IRoom> Rooms { get; }
|
||||||
|
|
||||||
public void AddRoom(int roomid) => this.AddRoom(roomid, true);
|
public IRoom AddRoom(int roomid) => this.AddRoom(roomid, true);
|
||||||
|
|
||||||
public void AddRoom(int roomid, bool enabled)
|
public IRoom AddRoom(int roomid, bool enabled)
|
||||||
{
|
{
|
||||||
lock (this.lockObject)
|
lock (this.lockObject)
|
||||||
{
|
{
|
||||||
this.logger.Debug("AddRoom {RoomId}, AutoRecord: {AutoRecord}", roomid, enabled);
|
this.logger.Debug("AddRoom {RoomId}, AutoRecord: {AutoRecord}", roomid, enabled);
|
||||||
var roomConfig = new RoomConfig { RoomId = roomid, AutoRecord = enabled };
|
var roomConfig = new RoomConfig { RoomId = roomid, AutoRecord = enabled };
|
||||||
this.AddRoom(roomConfig, 0);
|
var room = this.AddRoom(roomConfig, 0);
|
||||||
this.SaveConfig();
|
this.SaveConfig();
|
||||||
|
return room;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddRoom(RoomConfig roomConfig, int initDelayFactor)
|
private IRoom AddRoom(RoomConfig roomConfig, int initDelayFactor)
|
||||||
{
|
{
|
||||||
roomConfig.SetParent(this.Config.Global);
|
roomConfig.SetParent(this.Config.Global);
|
||||||
var room = this.roomFactory.CreateRoom(roomConfig, initDelayFactor);
|
var room = this.roomFactory.CreateRoom(roomConfig, initDelayFactor);
|
||||||
|
@ -85,6 +86,7 @@ namespace BililiveRecorder.Core
|
||||||
room.PropertyChanged += this.Room_PropertyChanged;
|
room.PropertyChanged += this.Room_PropertyChanged;
|
||||||
|
|
||||||
this.roomCollection.Add(room);
|
this.roomCollection.Add(room);
|
||||||
|
return room;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RemoveRoom(IRoom room)
|
public void RemoveRoom(IRoom room)
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net5.0</TargetFramework>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="GraphQL.Server.Core" Version="5.0.2" />
|
||||||
|
<PackageReference Include="GraphQL.Server.Transports.AspNetCore.SystemTextJson" Version="5.0.2" />
|
||||||
|
<PackageReference Include="GraphQL.Server.Transports.Subscriptions.WebSockets" Version="5.0.2" />
|
||||||
|
<PackageReference Include="GraphQL.Server.Ui.Altair" Version="5.0.2" />
|
||||||
|
<PackageReference Include="GraphQL.Server.Ui.GraphiQL" Version="5.0.2" />
|
||||||
|
<PackageReference Include="GraphQL.Server.Ui.Playground" Version="5.0.2" />
|
||||||
|
<PackageReference Include="GraphQL.Server.Ui.Voyager" Version="5.0.2" />
|
||||||
|
<PackageReference Include="GraphQL.SystemReactive" Version="4.5.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\BililiveRecorder.Core\BililiveRecorder.Core.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
229
BililiveRecorder.Web.Schemas/RecorderMutation.cs
Normal file
229
BililiveRecorder.Web.Schemas/RecorderMutation.cs
Normal file
|
@ -0,0 +1,229 @@
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using BililiveRecorder.Core;
|
||||||
|
using BililiveRecorder.Web.Schemas.Types;
|
||||||
|
using GraphQL;
|
||||||
|
using GraphQL.Types;
|
||||||
|
|
||||||
|
namespace BililiveRecorder.Web.Schemas
|
||||||
|
{
|
||||||
|
internal class RecorderMutation : ObjectGraphType
|
||||||
|
{
|
||||||
|
private readonly IRecorder recorder;
|
||||||
|
|
||||||
|
public RecorderMutation(IRecorder recorder)
|
||||||
|
{
|
||||||
|
this.recorder = recorder ?? throw new ArgumentNullException(nameof(recorder));
|
||||||
|
|
||||||
|
this.SetupFields();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetupFields()
|
||||||
|
{
|
||||||
|
this.Field<RoomType>("addRoom",
|
||||||
|
arguments: new QueryArguments(
|
||||||
|
new QueryArgument<IntGraphType> { Name = "roomId" },
|
||||||
|
new QueryArgument<BooleanGraphType> { Name = "autoRecord" }
|
||||||
|
),
|
||||||
|
resolve: context =>
|
||||||
|
{
|
||||||
|
var roomid = context.GetArgument<int>("roomId");
|
||||||
|
var enabled = context.GetArgument<bool>("autoRecord");
|
||||||
|
|
||||||
|
var room = this.recorder.Rooms.FirstOrDefault(x => x.RoomConfig.RoomId == roomid || x.ShortId == roomid);
|
||||||
|
|
||||||
|
if (room is not null)
|
||||||
|
{
|
||||||
|
context.Errors.Add(new ExecutionError("Room already exist.")
|
||||||
|
{
|
||||||
|
Code = "BREC_ROOM_DUPLICATE"
|
||||||
|
});
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return this.recorder.AddRoom(roomid, enabled);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.Field<RoomType>("removeRoom",
|
||||||
|
arguments: new QueryArguments(
|
||||||
|
new QueryArgument<GuidGraphType> { Name = "objectId" },
|
||||||
|
new QueryArgument<IntGraphType> { Name = "roomId" }
|
||||||
|
),
|
||||||
|
resolve: context =>
|
||||||
|
{
|
||||||
|
var objectId = context.GetArgument<Guid>("objectId");
|
||||||
|
var roomid = context.GetArgument<int>("roomId");
|
||||||
|
|
||||||
|
var room = objectId != default
|
||||||
|
? this.recorder.Rooms.FirstOrDefault(x => x.ObjectId == objectId)
|
||||||
|
: this.recorder.Rooms.FirstOrDefault(x => x.RoomConfig.RoomId == roomid || x.ShortId == roomid);
|
||||||
|
|
||||||
|
if (room is not null)
|
||||||
|
this.recorder.RemoveRoom(room);
|
||||||
|
else
|
||||||
|
context.Errors.Add(new ExecutionError("Room not found")
|
||||||
|
{
|
||||||
|
Code = "BREC_ROOM_NOT_FOUND"
|
||||||
|
});
|
||||||
|
|
||||||
|
return room;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.FieldAsync<RoomType>("refreshRoomInfo",
|
||||||
|
arguments: new QueryArguments(
|
||||||
|
new QueryArgument<GuidGraphType> { Name = "objectId" },
|
||||||
|
new QueryArgument<IntGraphType> { Name = "roomId" }
|
||||||
|
),
|
||||||
|
resolve: async context =>
|
||||||
|
{
|
||||||
|
var objectId = context.GetArgument<Guid>("objectId");
|
||||||
|
var roomid = context.GetArgument<int>("roomId");
|
||||||
|
|
||||||
|
var room = objectId != default
|
||||||
|
? this.recorder.Rooms.FirstOrDefault(x => x.ObjectId == objectId)
|
||||||
|
: this.recorder.Rooms.FirstOrDefault(x => x.RoomConfig.RoomId == roomid || x.ShortId == roomid);
|
||||||
|
|
||||||
|
if (room is not null)
|
||||||
|
await room.RefreshRoomInfoAsync().ConfigureAwait(false);
|
||||||
|
else
|
||||||
|
context.Errors.Add(new ExecutionError("Room not found")
|
||||||
|
{
|
||||||
|
Code = "BREC_ROOM_NOT_FOUND"
|
||||||
|
});
|
||||||
|
|
||||||
|
return room;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.Field<RoomType>("startRecording",
|
||||||
|
arguments: new QueryArguments(
|
||||||
|
new QueryArgument<GuidGraphType> { Name = "objectId" },
|
||||||
|
new QueryArgument<IntGraphType> { Name = "roomId" }
|
||||||
|
),
|
||||||
|
resolve: context =>
|
||||||
|
{
|
||||||
|
var objectId = context.GetArgument<Guid>("objectId");
|
||||||
|
var roomid = context.GetArgument<int>("roomId");
|
||||||
|
|
||||||
|
var room = objectId != default
|
||||||
|
? this.recorder.Rooms.FirstOrDefault(x => x.ObjectId == objectId)
|
||||||
|
: this.recorder.Rooms.FirstOrDefault(x => x.RoomConfig.RoomId == roomid || x.ShortId == roomid);
|
||||||
|
|
||||||
|
if (room is not null)
|
||||||
|
room.StartRecord();
|
||||||
|
else
|
||||||
|
context.Errors.Add(new ExecutionError("Room not found")
|
||||||
|
{
|
||||||
|
Code = "BREC_ROOM_NOT_FOUND"
|
||||||
|
});
|
||||||
|
|
||||||
|
return room;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.Field<RoomType>("stopRecording",
|
||||||
|
arguments: new QueryArguments(
|
||||||
|
new QueryArgument<GuidGraphType> { Name = "objectId" },
|
||||||
|
new QueryArgument<IntGraphType> { Name = "roomId" }
|
||||||
|
),
|
||||||
|
resolve: context =>
|
||||||
|
{
|
||||||
|
var objectId = context.GetArgument<Guid>("objectId");
|
||||||
|
var roomid = context.GetArgument<int>("roomId");
|
||||||
|
|
||||||
|
var room = objectId != default
|
||||||
|
? this.recorder.Rooms.FirstOrDefault(x => x.ObjectId == objectId)
|
||||||
|
: this.recorder.Rooms.FirstOrDefault(x => x.RoomConfig.RoomId == roomid || x.ShortId == roomid);
|
||||||
|
|
||||||
|
if (room is not null)
|
||||||
|
room.StopRecord();
|
||||||
|
else
|
||||||
|
context.Errors.Add(new ExecutionError("Room not found")
|
||||||
|
{
|
||||||
|
Code = "BREC_ROOM_NOT_FOUND"
|
||||||
|
});
|
||||||
|
|
||||||
|
return room;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.Field<RoomType>("splitRecordingOutput",
|
||||||
|
arguments: new QueryArguments(
|
||||||
|
new QueryArgument<GuidGraphType> { Name = "objectId" },
|
||||||
|
new QueryArgument<IntGraphType> { Name = "roomId" }
|
||||||
|
),
|
||||||
|
resolve: context =>
|
||||||
|
{
|
||||||
|
var objectId = context.GetArgument<Guid>("objectId");
|
||||||
|
var roomid = context.GetArgument<int>("roomId");
|
||||||
|
|
||||||
|
var room = objectId != default
|
||||||
|
? this.recorder.Rooms.FirstOrDefault(x => x.ObjectId == objectId)
|
||||||
|
: this.recorder.Rooms.FirstOrDefault(x => x.RoomConfig.RoomId == roomid || x.ShortId == roomid);
|
||||||
|
|
||||||
|
if (room is not null)
|
||||||
|
room.SplitOutput();
|
||||||
|
else
|
||||||
|
context.Errors.Add(new ExecutionError("Room not found")
|
||||||
|
{
|
||||||
|
Code = "BREC_ROOM_NOT_FOUND"
|
||||||
|
});
|
||||||
|
|
||||||
|
return room;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.Field<RoomType>("setRoomConfig",
|
||||||
|
arguments: new QueryArguments(
|
||||||
|
new QueryArgument<GuidGraphType> { Name = "objectId" },
|
||||||
|
new QueryArgument<IntGraphType> { Name = "roomId" },
|
||||||
|
new QueryArgument<SetRoomConfigType> { Name = "config" }
|
||||||
|
),
|
||||||
|
resolve: context =>
|
||||||
|
{
|
||||||
|
var objectId = context.GetArgument<Guid>("objectId");
|
||||||
|
var roomid = context.GetArgument<int>("roomId");
|
||||||
|
var config = context.GetArgument<SetRoomConfig>("config");
|
||||||
|
|
||||||
|
if (config is null)
|
||||||
|
{
|
||||||
|
context.Errors.Add(new ExecutionError("config can't be null"));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var room = objectId != default
|
||||||
|
? this.recorder.Rooms.FirstOrDefault(x => x.ObjectId == objectId)
|
||||||
|
: this.recorder.Rooms.FirstOrDefault(x => x.RoomConfig.RoomId == roomid || x.ShortId == roomid);
|
||||||
|
|
||||||
|
if (room is null)
|
||||||
|
context.Errors.Add(new ExecutionError("Room not found")
|
||||||
|
{
|
||||||
|
Code = "BREC_ROOM_NOT_FOUND"
|
||||||
|
});
|
||||||
|
else
|
||||||
|
config.ApplyTo(room.RoomConfig);
|
||||||
|
|
||||||
|
return room;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.Field<GlobalConfigType>("setConfig",
|
||||||
|
arguments: new QueryArguments(
|
||||||
|
new QueryArgument<SetGlobalConfigType> { Name = "config" }
|
||||||
|
),
|
||||||
|
resolve: context =>
|
||||||
|
{
|
||||||
|
var config = context.GetArgument<SetGlobalConfig>("config");
|
||||||
|
|
||||||
|
if (config is null)
|
||||||
|
{
|
||||||
|
context.Errors.Add(new ExecutionError("config can't be null"));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var recconfig = this.recorder.Config.Global;
|
||||||
|
|
||||||
|
config.ApplyTo(recconfig);
|
||||||
|
|
||||||
|
return recconfig;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
49
BililiveRecorder.Web.Schemas/RecorderQuery.cs
Normal file
49
BililiveRecorder.Web.Schemas/RecorderQuery.cs
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using BililiveRecorder.Core;
|
||||||
|
using BililiveRecorder.Core.Config.V2;
|
||||||
|
using BililiveRecorder.Web.Schemas.Types;
|
||||||
|
using GraphQL;
|
||||||
|
using GraphQL.Types;
|
||||||
|
|
||||||
|
namespace BililiveRecorder.Web.Schemas
|
||||||
|
{
|
||||||
|
internal class RecorderQuery : ObjectGraphType
|
||||||
|
{
|
||||||
|
private readonly IRecorder recorder;
|
||||||
|
|
||||||
|
public RecorderQuery(IRecorder recorder)
|
||||||
|
{
|
||||||
|
this.recorder = recorder ?? throw new ArgumentNullException(nameof(recorder));
|
||||||
|
|
||||||
|
this.SetupFields();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetupFields()
|
||||||
|
{
|
||||||
|
this.Field<GlobalConfigType>("config", resolve: context => this.recorder.Config.Global);
|
||||||
|
|
||||||
|
this.Field<DefaultConfigType>("defaultConfig", resolve: context => DefaultConfig.Instance);
|
||||||
|
|
||||||
|
this.Field<ListGraphType<RoomType>>("rooms", resolve: context => this.recorder.Rooms);
|
||||||
|
|
||||||
|
this.Field<RoomType>("room",
|
||||||
|
arguments: new QueryArguments(
|
||||||
|
new QueryArgument<IdGraphType> { Name = "objectId" },
|
||||||
|
new QueryArgument<IntGraphType> { Name = "roomId" }
|
||||||
|
),
|
||||||
|
resolve: context =>
|
||||||
|
{
|
||||||
|
var objectId = context.GetArgument<Guid>("objectId");
|
||||||
|
var roomid = context.GetArgument<int>("roomId");
|
||||||
|
|
||||||
|
var room = objectId != default
|
||||||
|
? this.recorder.Rooms.FirstOrDefault(x => x.ObjectId == objectId)
|
||||||
|
: this.recorder.Rooms.FirstOrDefault(x => x.RoomConfig.RoomId == roomid || x.ShortId == roomid);
|
||||||
|
|
||||||
|
return room;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
16
BililiveRecorder.Web.Schemas/RecorderSchema.cs
Normal file
16
BililiveRecorder.Web.Schemas/RecorderSchema.cs
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
using System;
|
||||||
|
using BililiveRecorder.Core;
|
||||||
|
using GraphQL.Types;
|
||||||
|
|
||||||
|
namespace BililiveRecorder.Web.Schemas
|
||||||
|
{
|
||||||
|
public class RecorderSchema : Schema
|
||||||
|
{
|
||||||
|
public RecorderSchema(IServiceProvider services, IRecorder recorder) : base(services)
|
||||||
|
{
|
||||||
|
this.Query = new RecorderQuery(recorder);
|
||||||
|
this.Mutation = new RecorderMutation(recorder);
|
||||||
|
//this.Subscription = new RecorderSubscription();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
8
BililiveRecorder.Web.Schemas/RecorderSubscription.cs
Normal file
8
BililiveRecorder.Web.Schemas/RecorderSubscription.cs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
using GraphQL.Types;
|
||||||
|
|
||||||
|
namespace BililiveRecorder.Web.Schemas
|
||||||
|
{
|
||||||
|
internal class RecorderSubscription : ObjectGraphType
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
214
BililiveRecorder.Web.Schemas/Types/Config.gen.cs
Normal file
214
BililiveRecorder.Web.Schemas/Types/Config.gen.cs
Normal file
|
@ -0,0 +1,214 @@
|
||||||
|
// ******************************
|
||||||
|
// GENERATED CODE, DO NOT EDIT MANUALLY.
|
||||||
|
// SEE /config_gen/README.md
|
||||||
|
// ******************************
|
||||||
|
|
||||||
|
using BililiveRecorder.Core.Config.V2;
|
||||||
|
using GraphQL.Types;
|
||||||
|
using HierarchicalPropertyDefault;
|
||||||
|
#nullable enable
|
||||||
|
namespace BililiveRecorder.Web.Schemas.Types
|
||||||
|
{
|
||||||
|
internal class RoomConfigType : ObjectGraphType<RoomConfig>
|
||||||
|
{
|
||||||
|
public RoomConfigType()
|
||||||
|
{
|
||||||
|
this.Field(x => x.RoomId);
|
||||||
|
this.Field(x => x.AutoRecord);
|
||||||
|
this.Field(x => x.OptionalRecordMode, type: typeof(HierarchicalOptionalType<RecordMode>));
|
||||||
|
this.Field(x => x.OptionalCuttingMode, type: typeof(HierarchicalOptionalType<CuttingMode>));
|
||||||
|
this.Field(x => x.OptionalCuttingNumber, type: typeof(HierarchicalOptionalType<uint>));
|
||||||
|
this.Field(x => x.OptionalRecordDanmaku, type: typeof(HierarchicalOptionalType<bool>));
|
||||||
|
this.Field(x => x.OptionalRecordDanmakuRaw, type: typeof(HierarchicalOptionalType<bool>));
|
||||||
|
this.Field(x => x.OptionalRecordDanmakuSuperChat, type: typeof(HierarchicalOptionalType<bool>));
|
||||||
|
this.Field(x => x.OptionalRecordDanmakuGift, type: typeof(HierarchicalOptionalType<bool>));
|
||||||
|
this.Field(x => x.OptionalRecordDanmakuGuard, type: typeof(HierarchicalOptionalType<bool>));
|
||||||
|
this.Field(x => x.OptionalRecordingQuality, type: typeof(HierarchicalOptionalType<string>));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class GlobalConfigType : ObjectGraphType<GlobalConfig>
|
||||||
|
{
|
||||||
|
public GlobalConfigType()
|
||||||
|
{
|
||||||
|
this.Field(x => x.OptionalRecordMode, type: typeof(HierarchicalOptionalType<RecordMode>));
|
||||||
|
this.Field(x => x.OptionalCuttingMode, type: typeof(HierarchicalOptionalType<CuttingMode>));
|
||||||
|
this.Field(x => x.OptionalCuttingNumber, type: typeof(HierarchicalOptionalType<uint>));
|
||||||
|
this.Field(x => x.OptionalRecordDanmaku, type: typeof(HierarchicalOptionalType<bool>));
|
||||||
|
this.Field(x => x.OptionalRecordDanmakuRaw, type: typeof(HierarchicalOptionalType<bool>));
|
||||||
|
this.Field(x => x.OptionalRecordDanmakuSuperChat, type: typeof(HierarchicalOptionalType<bool>));
|
||||||
|
this.Field(x => x.OptionalRecordDanmakuGift, type: typeof(HierarchicalOptionalType<bool>));
|
||||||
|
this.Field(x => x.OptionalRecordDanmakuGuard, type: typeof(HierarchicalOptionalType<bool>));
|
||||||
|
this.Field(x => x.OptionalRecordingQuality, type: typeof(HierarchicalOptionalType<string>));
|
||||||
|
this.Field(x => x.OptionalRecordFilenameFormat, type: typeof(HierarchicalOptionalType<string>));
|
||||||
|
this.Field(x => x.OptionalWebHookUrls, type: typeof(HierarchicalOptionalType<string>));
|
||||||
|
this.Field(x => x.OptionalWebHookUrlsV2, type: typeof(HierarchicalOptionalType<string>));
|
||||||
|
this.Field(x => x.OptionalWpfShowTitleAndArea, type: typeof(HierarchicalOptionalType<bool>));
|
||||||
|
this.Field(x => x.OptionalCookie, type: typeof(HierarchicalOptionalType<string>));
|
||||||
|
this.Field(x => x.OptionalLiveApiHost, type: typeof(HierarchicalOptionalType<string>));
|
||||||
|
this.Field(x => x.OptionalTimingCheckInterval, type: typeof(HierarchicalOptionalType<uint>));
|
||||||
|
this.Field(x => x.OptionalTimingStreamRetry, type: typeof(HierarchicalOptionalType<uint>));
|
||||||
|
this.Field(x => x.OptionalTimingStreamRetryNoQn, type: typeof(HierarchicalOptionalType<uint>));
|
||||||
|
this.Field(x => x.OptionalTimingStreamConnect, type: typeof(HierarchicalOptionalType<uint>));
|
||||||
|
this.Field(x => x.OptionalTimingDanmakuRetry, type: typeof(HierarchicalOptionalType<uint>));
|
||||||
|
this.Field(x => x.OptionalTimingWatchdogTimeout, type: typeof(HierarchicalOptionalType<uint>));
|
||||||
|
this.Field(x => x.OptionalRecordDanmakuFlushInterval, type: typeof(HierarchicalOptionalType<uint>));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class DefaultConfigType : ObjectGraphType<DefaultConfig>
|
||||||
|
{
|
||||||
|
public DefaultConfigType()
|
||||||
|
{
|
||||||
|
this.Field(x => x.RecordMode);
|
||||||
|
this.Field(x => x.CuttingMode);
|
||||||
|
this.Field(x => x.CuttingNumber);
|
||||||
|
this.Field(x => x.RecordDanmaku);
|
||||||
|
this.Field(x => x.RecordDanmakuRaw);
|
||||||
|
this.Field(x => x.RecordDanmakuSuperChat);
|
||||||
|
this.Field(x => x.RecordDanmakuGift);
|
||||||
|
this.Field(x => x.RecordDanmakuGuard);
|
||||||
|
this.Field(x => x.RecordingQuality);
|
||||||
|
this.Field(x => x.RecordFilenameFormat);
|
||||||
|
this.Field(x => x.WebHookUrls);
|
||||||
|
this.Field(x => x.WebHookUrlsV2);
|
||||||
|
this.Field(x => x.WpfShowTitleAndArea);
|
||||||
|
this.Field(x => x.Cookie);
|
||||||
|
this.Field(x => x.LiveApiHost);
|
||||||
|
this.Field(x => x.TimingCheckInterval);
|
||||||
|
this.Field(x => x.TimingStreamRetry);
|
||||||
|
this.Field(x => x.TimingStreamRetryNoQn);
|
||||||
|
this.Field(x => x.TimingStreamConnect);
|
||||||
|
this.Field(x => x.TimingDanmakuRetry);
|
||||||
|
this.Field(x => x.TimingWatchdogTimeout);
|
||||||
|
this.Field(x => x.RecordDanmakuFlushInterval);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class SetRoomConfig
|
||||||
|
{
|
||||||
|
public bool? AutoRecord { get; set; }
|
||||||
|
public Optional<RecordMode>? OptionalRecordMode { get; set; }
|
||||||
|
public Optional<CuttingMode>? OptionalCuttingMode { get; set; }
|
||||||
|
public Optional<uint>? OptionalCuttingNumber { get; set; }
|
||||||
|
public Optional<bool>? OptionalRecordDanmaku { get; set; }
|
||||||
|
public Optional<bool>? OptionalRecordDanmakuRaw { get; set; }
|
||||||
|
public Optional<bool>? OptionalRecordDanmakuSuperChat { get; set; }
|
||||||
|
public Optional<bool>? OptionalRecordDanmakuGift { get; set; }
|
||||||
|
public Optional<bool>? OptionalRecordDanmakuGuard { get; set; }
|
||||||
|
public Optional<string?>? OptionalRecordingQuality { get; set; }
|
||||||
|
|
||||||
|
public void ApplyTo(RoomConfig config)
|
||||||
|
{
|
||||||
|
if (this.AutoRecord.HasValue) config.AutoRecord = this.AutoRecord.Value;
|
||||||
|
if (this.OptionalRecordMode.HasValue) config.OptionalRecordMode = this.OptionalRecordMode.Value;
|
||||||
|
if (this.OptionalCuttingMode.HasValue) config.OptionalCuttingMode = this.OptionalCuttingMode.Value;
|
||||||
|
if (this.OptionalCuttingNumber.HasValue) config.OptionalCuttingNumber = this.OptionalCuttingNumber.Value;
|
||||||
|
if (this.OptionalRecordDanmaku.HasValue) config.OptionalRecordDanmaku = this.OptionalRecordDanmaku.Value;
|
||||||
|
if (this.OptionalRecordDanmakuRaw.HasValue) config.OptionalRecordDanmakuRaw = this.OptionalRecordDanmakuRaw.Value;
|
||||||
|
if (this.OptionalRecordDanmakuSuperChat.HasValue) config.OptionalRecordDanmakuSuperChat = this.OptionalRecordDanmakuSuperChat.Value;
|
||||||
|
if (this.OptionalRecordDanmakuGift.HasValue) config.OptionalRecordDanmakuGift = this.OptionalRecordDanmakuGift.Value;
|
||||||
|
if (this.OptionalRecordDanmakuGuard.HasValue) config.OptionalRecordDanmakuGuard = this.OptionalRecordDanmakuGuard.Value;
|
||||||
|
if (this.OptionalRecordingQuality.HasValue) config.OptionalRecordingQuality = this.OptionalRecordingQuality.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class SetRoomConfigType : InputObjectGraphType<SetRoomConfig>
|
||||||
|
{
|
||||||
|
public SetRoomConfigType()
|
||||||
|
{
|
||||||
|
this.Field(x => x.AutoRecord, nullable: true);
|
||||||
|
this.Field(x => x.OptionalRecordMode, nullable: true, type: typeof(HierarchicalOptionalInputType<RecordMode>));
|
||||||
|
this.Field(x => x.OptionalCuttingMode, nullable: true, type: typeof(HierarchicalOptionalInputType<CuttingMode>));
|
||||||
|
this.Field(x => x.OptionalCuttingNumber, nullable: true, type: typeof(HierarchicalOptionalInputType<uint>));
|
||||||
|
this.Field(x => x.OptionalRecordDanmaku, nullable: true, type: typeof(HierarchicalOptionalInputType<bool>));
|
||||||
|
this.Field(x => x.OptionalRecordDanmakuRaw, nullable: true, type: typeof(HierarchicalOptionalInputType<bool>));
|
||||||
|
this.Field(x => x.OptionalRecordDanmakuSuperChat, nullable: true, type: typeof(HierarchicalOptionalInputType<bool>));
|
||||||
|
this.Field(x => x.OptionalRecordDanmakuGift, nullable: true, type: typeof(HierarchicalOptionalInputType<bool>));
|
||||||
|
this.Field(x => x.OptionalRecordDanmakuGuard, nullable: true, type: typeof(HierarchicalOptionalInputType<bool>));
|
||||||
|
this.Field(x => x.OptionalRecordingQuality, nullable: true, type: typeof(HierarchicalOptionalInputType<string>));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class SetGlobalConfig
|
||||||
|
{
|
||||||
|
public Optional<RecordMode>? OptionalRecordMode { get; set; }
|
||||||
|
public Optional<CuttingMode>? OptionalCuttingMode { get; set; }
|
||||||
|
public Optional<uint>? OptionalCuttingNumber { get; set; }
|
||||||
|
public Optional<bool>? OptionalRecordDanmaku { get; set; }
|
||||||
|
public Optional<bool>? OptionalRecordDanmakuRaw { get; set; }
|
||||||
|
public Optional<bool>? OptionalRecordDanmakuSuperChat { get; set; }
|
||||||
|
public Optional<bool>? OptionalRecordDanmakuGift { get; set; }
|
||||||
|
public Optional<bool>? OptionalRecordDanmakuGuard { get; set; }
|
||||||
|
public Optional<string?>? OptionalRecordingQuality { get; set; }
|
||||||
|
public Optional<string?>? OptionalRecordFilenameFormat { get; set; }
|
||||||
|
public Optional<string?>? OptionalWebHookUrls { get; set; }
|
||||||
|
public Optional<string?>? OptionalWebHookUrlsV2 { get; set; }
|
||||||
|
public Optional<bool>? OptionalWpfShowTitleAndArea { get; set; }
|
||||||
|
public Optional<string?>? OptionalCookie { get; set; }
|
||||||
|
public Optional<string?>? OptionalLiveApiHost { get; set; }
|
||||||
|
public Optional<uint>? OptionalTimingCheckInterval { get; set; }
|
||||||
|
public Optional<uint>? OptionalTimingStreamRetry { get; set; }
|
||||||
|
public Optional<uint>? OptionalTimingStreamRetryNoQn { get; set; }
|
||||||
|
public Optional<uint>? OptionalTimingStreamConnect { get; set; }
|
||||||
|
public Optional<uint>? OptionalTimingDanmakuRetry { get; set; }
|
||||||
|
public Optional<uint>? OptionalTimingWatchdogTimeout { get; set; }
|
||||||
|
public Optional<uint>? OptionalRecordDanmakuFlushInterval { get; set; }
|
||||||
|
|
||||||
|
public void ApplyTo(GlobalConfig config)
|
||||||
|
{
|
||||||
|
if (this.OptionalRecordMode.HasValue) config.OptionalRecordMode = this.OptionalRecordMode.Value;
|
||||||
|
if (this.OptionalCuttingMode.HasValue) config.OptionalCuttingMode = this.OptionalCuttingMode.Value;
|
||||||
|
if (this.OptionalCuttingNumber.HasValue) config.OptionalCuttingNumber = this.OptionalCuttingNumber.Value;
|
||||||
|
if (this.OptionalRecordDanmaku.HasValue) config.OptionalRecordDanmaku = this.OptionalRecordDanmaku.Value;
|
||||||
|
if (this.OptionalRecordDanmakuRaw.HasValue) config.OptionalRecordDanmakuRaw = this.OptionalRecordDanmakuRaw.Value;
|
||||||
|
if (this.OptionalRecordDanmakuSuperChat.HasValue) config.OptionalRecordDanmakuSuperChat = this.OptionalRecordDanmakuSuperChat.Value;
|
||||||
|
if (this.OptionalRecordDanmakuGift.HasValue) config.OptionalRecordDanmakuGift = this.OptionalRecordDanmakuGift.Value;
|
||||||
|
if (this.OptionalRecordDanmakuGuard.HasValue) config.OptionalRecordDanmakuGuard = this.OptionalRecordDanmakuGuard.Value;
|
||||||
|
if (this.OptionalRecordingQuality.HasValue) config.OptionalRecordingQuality = this.OptionalRecordingQuality.Value;
|
||||||
|
if (this.OptionalRecordFilenameFormat.HasValue) config.OptionalRecordFilenameFormat = this.OptionalRecordFilenameFormat.Value;
|
||||||
|
if (this.OptionalWebHookUrls.HasValue) config.OptionalWebHookUrls = this.OptionalWebHookUrls.Value;
|
||||||
|
if (this.OptionalWebHookUrlsV2.HasValue) config.OptionalWebHookUrlsV2 = this.OptionalWebHookUrlsV2.Value;
|
||||||
|
if (this.OptionalWpfShowTitleAndArea.HasValue) config.OptionalWpfShowTitleAndArea = this.OptionalWpfShowTitleAndArea.Value;
|
||||||
|
if (this.OptionalCookie.HasValue) config.OptionalCookie = this.OptionalCookie.Value;
|
||||||
|
if (this.OptionalLiveApiHost.HasValue) config.OptionalLiveApiHost = this.OptionalLiveApiHost.Value;
|
||||||
|
if (this.OptionalTimingCheckInterval.HasValue) config.OptionalTimingCheckInterval = this.OptionalTimingCheckInterval.Value;
|
||||||
|
if (this.OptionalTimingStreamRetry.HasValue) config.OptionalTimingStreamRetry = this.OptionalTimingStreamRetry.Value;
|
||||||
|
if (this.OptionalTimingStreamRetryNoQn.HasValue) config.OptionalTimingStreamRetryNoQn = this.OptionalTimingStreamRetryNoQn.Value;
|
||||||
|
if (this.OptionalTimingStreamConnect.HasValue) config.OptionalTimingStreamConnect = this.OptionalTimingStreamConnect.Value;
|
||||||
|
if (this.OptionalTimingDanmakuRetry.HasValue) config.OptionalTimingDanmakuRetry = this.OptionalTimingDanmakuRetry.Value;
|
||||||
|
if (this.OptionalTimingWatchdogTimeout.HasValue) config.OptionalTimingWatchdogTimeout = this.OptionalTimingWatchdogTimeout.Value;
|
||||||
|
if (this.OptionalRecordDanmakuFlushInterval.HasValue) config.OptionalRecordDanmakuFlushInterval = this.OptionalRecordDanmakuFlushInterval.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class SetGlobalConfigType : InputObjectGraphType<SetGlobalConfig>
|
||||||
|
{
|
||||||
|
public SetGlobalConfigType()
|
||||||
|
{
|
||||||
|
this.Field(x => x.OptionalRecordMode, nullable: true, type: typeof(HierarchicalOptionalInputType<RecordMode>));
|
||||||
|
this.Field(x => x.OptionalCuttingMode, nullable: true, type: typeof(HierarchicalOptionalInputType<CuttingMode>));
|
||||||
|
this.Field(x => x.OptionalCuttingNumber, nullable: true, type: typeof(HierarchicalOptionalInputType<uint>));
|
||||||
|
this.Field(x => x.OptionalRecordDanmaku, nullable: true, type: typeof(HierarchicalOptionalInputType<bool>));
|
||||||
|
this.Field(x => x.OptionalRecordDanmakuRaw, nullable: true, type: typeof(HierarchicalOptionalInputType<bool>));
|
||||||
|
this.Field(x => x.OptionalRecordDanmakuSuperChat, nullable: true, type: typeof(HierarchicalOptionalInputType<bool>));
|
||||||
|
this.Field(x => x.OptionalRecordDanmakuGift, nullable: true, type: typeof(HierarchicalOptionalInputType<bool>));
|
||||||
|
this.Field(x => x.OptionalRecordDanmakuGuard, nullable: true, type: typeof(HierarchicalOptionalInputType<bool>));
|
||||||
|
this.Field(x => x.OptionalRecordingQuality, nullable: true, type: typeof(HierarchicalOptionalInputType<string>));
|
||||||
|
this.Field(x => x.OptionalRecordFilenameFormat, nullable: true, type: typeof(HierarchicalOptionalInputType<string>));
|
||||||
|
this.Field(x => x.OptionalWebHookUrls, nullable: true, type: typeof(HierarchicalOptionalInputType<string>));
|
||||||
|
this.Field(x => x.OptionalWebHookUrlsV2, nullable: true, type: typeof(HierarchicalOptionalInputType<string>));
|
||||||
|
this.Field(x => x.OptionalWpfShowTitleAndArea, nullable: true, type: typeof(HierarchicalOptionalInputType<bool>));
|
||||||
|
this.Field(x => x.OptionalCookie, nullable: true, type: typeof(HierarchicalOptionalInputType<string>));
|
||||||
|
this.Field(x => x.OptionalLiveApiHost, nullable: true, type: typeof(HierarchicalOptionalInputType<string>));
|
||||||
|
this.Field(x => x.OptionalTimingCheckInterval, nullable: true, type: typeof(HierarchicalOptionalInputType<uint>));
|
||||||
|
this.Field(x => x.OptionalTimingStreamRetry, nullable: true, type: typeof(HierarchicalOptionalInputType<uint>));
|
||||||
|
this.Field(x => x.OptionalTimingStreamRetryNoQn, nullable: true, type: typeof(HierarchicalOptionalInputType<uint>));
|
||||||
|
this.Field(x => x.OptionalTimingStreamConnect, nullable: true, type: typeof(HierarchicalOptionalInputType<uint>));
|
||||||
|
this.Field(x => x.OptionalTimingDanmakuRetry, nullable: true, type: typeof(HierarchicalOptionalInputType<uint>));
|
||||||
|
this.Field(x => x.OptionalTimingWatchdogTimeout, nullable: true, type: typeof(HierarchicalOptionalInputType<uint>));
|
||||||
|
this.Field(x => x.OptionalRecordDanmakuFlushInterval, nullable: true, type: typeof(HierarchicalOptionalInputType<uint>));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
9
BililiveRecorder.Web.Schemas/Types/CuttingModeEnum.cs
Normal file
9
BililiveRecorder.Web.Schemas/Types/CuttingModeEnum.cs
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
using BililiveRecorder.Core.Config.V2;
|
||||||
|
using GraphQL.Types;
|
||||||
|
|
||||||
|
namespace BililiveRecorder.Web.Schemas.Types
|
||||||
|
{
|
||||||
|
public class CuttingModeEnum : EnumerationGraphType<CuttingMode>
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
using GraphQL.Types;
|
||||||
|
using HierarchicalPropertyDefault;
|
||||||
|
|
||||||
|
namespace BililiveRecorder.Web.Schemas.Types
|
||||||
|
{
|
||||||
|
public class HierarchicalOptionalInputType<TValue> : InputObjectGraphType<Optional<TValue>>
|
||||||
|
{
|
||||||
|
public HierarchicalOptionalInputType()
|
||||||
|
{
|
||||||
|
this.Name = "HierarchicalOptionalInput_" + typeof(TValue).Name + "_Type";
|
||||||
|
|
||||||
|
this.Field(x => x.HasValue)
|
||||||
|
.Description("Use 'value' when 'hasValue' is true, or use the value from parent object when 'hasValue' is false.");
|
||||||
|
|
||||||
|
this.Field(x => x.Value, nullable: typeof(TValue) == typeof(string))
|
||||||
|
.Description("NOTE: The value of this field is ignored when 'hasValue' is false.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
using GraphQL.Types;
|
||||||
|
using HierarchicalPropertyDefault;
|
||||||
|
|
||||||
|
namespace BililiveRecorder.Web.Schemas.Types
|
||||||
|
{
|
||||||
|
public class HierarchicalOptionalType<TValue> : ObjectGraphType<Optional<TValue>>
|
||||||
|
{
|
||||||
|
public HierarchicalOptionalType()
|
||||||
|
{
|
||||||
|
this.Name = "HierarchicalOptional_" + typeof(TValue).Name + "_Type";
|
||||||
|
|
||||||
|
this.Field(x => x.HasValue)
|
||||||
|
.Description("Use 'value' when 'hasValue' is true, or use the value from parent object when 'hasValue' is false.");
|
||||||
|
|
||||||
|
this.Field(x => x.Value, nullable: typeof(TValue) == typeof(string))
|
||||||
|
.Description("NOTE: The value of this field is ignored when 'hasValue' is false.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
9
BililiveRecorder.Web.Schemas/Types/RecordModeEnum.cs
Normal file
9
BililiveRecorder.Web.Schemas/Types/RecordModeEnum.cs
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
using BililiveRecorder.Core.Config.V2;
|
||||||
|
using GraphQL.Types;
|
||||||
|
|
||||||
|
namespace BililiveRecorder.Web.Schemas.Types
|
||||||
|
{
|
||||||
|
public class RecordModeEnum : EnumerationGraphType<RecordMode>
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
19
BililiveRecorder.Web.Schemas/Types/RecordingStatsType.cs
Normal file
19
BililiveRecorder.Web.Schemas/Types/RecordingStatsType.cs
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
using BililiveRecorder.Core;
|
||||||
|
using GraphQL.Types;
|
||||||
|
|
||||||
|
namespace BililiveRecorder.Web.Schemas.Types
|
||||||
|
{
|
||||||
|
public class RecordingStatsType : ObjectGraphType<RecordingStats>
|
||||||
|
{
|
||||||
|
public RecordingStatsType()
|
||||||
|
{
|
||||||
|
this.Field(x => x.NetworkMbps);
|
||||||
|
this.Field(x => x.SessionDuration);
|
||||||
|
this.Field(x => x.FileMaxTimestamp);
|
||||||
|
this.Field(x => x.SessionMaxTimestamp);
|
||||||
|
this.Field(x => x.DurationRatio);
|
||||||
|
this.Field(x => x.TotalInputBytes);
|
||||||
|
this.Field(x => x.TotalOutputBytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
24
BililiveRecorder.Web.Schemas/Types/RoomType.cs
Normal file
24
BililiveRecorder.Web.Schemas/Types/RoomType.cs
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
using BililiveRecorder.Core;
|
||||||
|
using GraphQL.Types;
|
||||||
|
|
||||||
|
namespace BililiveRecorder.Web.Schemas.Types
|
||||||
|
{
|
||||||
|
internal class RoomType : ObjectGraphType<IRoom>
|
||||||
|
{
|
||||||
|
public RoomType()
|
||||||
|
{
|
||||||
|
this.Field(x => x.ObjectId);
|
||||||
|
this.Field(x => x.RoomConfig, type: typeof(RoomConfigType));
|
||||||
|
this.Field(x => x.ShortId);
|
||||||
|
this.Field(x => x.Name);
|
||||||
|
this.Field(x => x.Title);
|
||||||
|
this.Field(x => x.AreaNameParent);
|
||||||
|
this.Field(x => x.AreaNameChild);
|
||||||
|
this.Field(x => x.Recording);
|
||||||
|
this.Field(x => x.Streaming);
|
||||||
|
this.Field(x => x.AutoRecordForThisSession);
|
||||||
|
this.Field(x => x.DanmakuConnected);
|
||||||
|
this.Field(x => x.Stats, type: typeof(RecordingStatsType));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
15
BililiveRecorder.Web/BililiveRecorder.Web.csproj
Normal file
15
BililiveRecorder.Web/BililiveRecorder.Web.csproj
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net5.0</TargetFramework>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="GraphQL.Server.Transports.AspNetCore.NewtonsoftJson" Version="5.0.2" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\BililiveRecorder.Web.Schemas\BililiveRecorder.Web.Schemas.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
65
BililiveRecorder.Web/FakeRecorderForWeb.cs
Normal file
65
BililiveRecorder.Web/FakeRecorderForWeb.cs
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using BililiveRecorder.Core;
|
||||||
|
using BililiveRecorder.Core.Config.V2;
|
||||||
|
using BililiveRecorder.Core.Event;
|
||||||
|
|
||||||
|
namespace BililiveRecorder.Web
|
||||||
|
{
|
||||||
|
internal class FakeRecorderForWeb : IRecorder
|
||||||
|
{
|
||||||
|
private bool disposedValue;
|
||||||
|
|
||||||
|
public ConfigV2 Config { get; } = new ConfigV2();
|
||||||
|
|
||||||
|
public ReadOnlyObservableCollection<IRoom> Rooms { get; } = new ReadOnlyObservableCollection<IRoom>(new ObservableCollection<IRoom>());
|
||||||
|
|
||||||
|
public event EventHandler<AggregatedRoomEventArgs<RecordSessionStartedEventArgs>>? RecordSessionStarted;
|
||||||
|
public event EventHandler<AggregatedRoomEventArgs<RecordSessionEndedEventArgs>>? RecordSessionEnded;
|
||||||
|
public event EventHandler<AggregatedRoomEventArgs<RecordFileOpeningEventArgs>>? RecordFileOpening;
|
||||||
|
public event EventHandler<AggregatedRoomEventArgs<RecordFileClosedEventArgs>>? RecordFileClosed;
|
||||||
|
public event EventHandler<AggregatedRoomEventArgs<NetworkingStatsEventArgs>>? NetworkingStats;
|
||||||
|
public event EventHandler<AggregatedRoomEventArgs<RecordingStatsEventArgs>>? RecordingStats;
|
||||||
|
public event PropertyChangedEventHandler? PropertyChanged;
|
||||||
|
|
||||||
|
public IRoom AddRoom(int roomid) => null!;
|
||||||
|
|
||||||
|
public IRoom AddRoom(int roomid, bool enabled) => null!;
|
||||||
|
|
||||||
|
public void RemoveRoom(IRoom room)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
public void SaveConfig()
|
||||||
|
{ }
|
||||||
|
|
||||||
|
protected virtual void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (!this.disposedValue)
|
||||||
|
{
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
// TODO: dispose managed state (managed objects)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: free unmanaged resources (unmanaged objects) and override finalizer
|
||||||
|
// TODO: set large fields to null
|
||||||
|
this.disposedValue = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources
|
||||||
|
// ~FakeRecorderForWeb()
|
||||||
|
// {
|
||||||
|
// // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||||
|
// Dispose(disposing: false);
|
||||||
|
// }
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||||
|
this.Dispose(disposing: true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
23
BililiveRecorder.Web/Program.cs
Normal file
23
BililiveRecorder.Web/Program.cs
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace BililiveRecorder.Web
|
||||||
|
{
|
||||||
|
public class Program
|
||||||
|
{
|
||||||
|
public static void Main(string[] args) => CreateHostBuilder(args).Build().Run();
|
||||||
|
|
||||||
|
public static IHostBuilder CreateHostBuilder(string[] args) =>
|
||||||
|
Host.CreateDefaultBuilder(args)
|
||||||
|
.ConfigureWebHostDefaults(webBuilder =>
|
||||||
|
{
|
||||||
|
webBuilder.UseStartup<Startup>();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
28
BililiveRecorder.Web/Properties/launchSettings.json
Normal file
28
BililiveRecorder.Web/Properties/launchSettings.json
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
{
|
||||||
|
"iisSettings": {
|
||||||
|
"windowsAuthentication": false,
|
||||||
|
"anonymousAuthentication": true,
|
||||||
|
"iisExpress": {
|
||||||
|
"applicationUrl": "http://localhost:9644",
|
||||||
|
"sslPort": 44313
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"profiles": {
|
||||||
|
"IIS Express": {
|
||||||
|
"commandName": "IISExpress",
|
||||||
|
"launchBrowser": true,
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"BililiveRecorder.Web": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"dotnetRunMessages": "true",
|
||||||
|
"launchBrowser": true,
|
||||||
|
"applicationUrl": "https://localhost:5001;http://localhost:5000",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
82
BililiveRecorder.Web/Startup.cs
Normal file
82
BililiveRecorder.Web/Startup.cs
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
using System.Diagnostics;
|
||||||
|
using BililiveRecorder.Core;
|
||||||
|
using BililiveRecorder.Web.Schemas;
|
||||||
|
using GraphQL;
|
||||||
|
using GraphQL.Server;
|
||||||
|
using GraphQL.Types;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Server.Kestrel.Core;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace BililiveRecorder.Web
|
||||||
|
{
|
||||||
|
public class Startup
|
||||||
|
{
|
||||||
|
// TODO 减少引用的依赖数量
|
||||||
|
|
||||||
|
public Startup(IConfiguration configuration, IWebHostEnvironment environment)
|
||||||
|
{
|
||||||
|
this.Configuration = configuration;
|
||||||
|
this.Environment = environment;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IConfiguration Configuration { get; }
|
||||||
|
|
||||||
|
public IWebHostEnvironment Environment { get; }
|
||||||
|
|
||||||
|
// This method gets called by the runtime. Use this method to add services to the container.
|
||||||
|
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
|
||||||
|
public void ConfigureServices(IServiceCollection services)
|
||||||
|
{
|
||||||
|
services.Configure<KestrelServerOptions>(o => o.AllowSynchronousIO = true);
|
||||||
|
|
||||||
|
services.TryAddSingleton<IRecorder>(new FakeRecorderForWeb());
|
||||||
|
|
||||||
|
services
|
||||||
|
.AddCors(o => o.AddDefaultPolicy(p => p.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader()))
|
||||||
|
.AddSingleton<RecorderSchema>()
|
||||||
|
.AddSingleton(typeof(EnumerationGraphType<>))
|
||||||
|
.AddSingleton<IDocumentExecuter, SubscriptionDocumentExecuter>()
|
||||||
|
.AddGraphQL((options, provider) =>
|
||||||
|
{
|
||||||
|
options.EnableMetrics = this.Environment.IsDevelopment() || Debugger.IsAttached;
|
||||||
|
var logger = provider.GetRequiredService<ILogger<Startup>>();
|
||||||
|
options.UnhandledExceptionDelegate = ctx => logger.LogWarning(ctx.OriginalException, "Unhandled GraphQL Exception");
|
||||||
|
})
|
||||||
|
//.AddSystemTextJson()
|
||||||
|
.AddNewtonsoftJson()
|
||||||
|
.AddWebSockets()
|
||||||
|
.AddGraphTypes(typeof(RecorderSchema))
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||||
|
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) => app
|
||||||
|
.UseCors()
|
||||||
|
.UseWebSockets()
|
||||||
|
.UseGraphQLWebSockets<RecorderSchema>()
|
||||||
|
.UseGraphQL<RecorderSchema>()
|
||||||
|
.UseGraphQLPlayground()
|
||||||
|
.UseGraphQLGraphiQL()
|
||||||
|
.UseGraphQLAltair()
|
||||||
|
.UseGraphQLVoyager()
|
||||||
|
.Use(next => async context =>
|
||||||
|
{
|
||||||
|
if (context.Request.Path == "/")
|
||||||
|
{
|
||||||
|
await context.Response.WriteAsync(@"<h1>to be done</h1><style>a{margin:5px}</style><a href=""/ui/playground"">Playground</a><a href=""/ui/graphiql"">GraphiQL</a><a href=""/ui/altair"">Altair</a><a href=""/ui/voyager"">Voyager</a>").ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
context.Response.Redirect("/");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
;
|
||||||
|
}
|
||||||
|
}
|
9
BililiveRecorder.Web/appsettings.Development.json
Normal file
9
BililiveRecorder.Web/appsettings.Development.json
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft": "Warning",
|
||||||
|
"Microsoft.Hosting.Lifetime": "Information"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
10
BililiveRecorder.Web/appsettings.json
Normal file
10
BililiveRecorder.Web/appsettings.json
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft": "Warning",
|
||||||
|
"Microsoft.Hosting.Lifetime": "Information"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"AllowedHosts": "*"
|
||||||
|
}
|
|
@ -29,6 +29,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BililiveRecorder.ToolBox",
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BililiveRecorder.Flv.Tests", "test\BililiveRecorder.Flv.Tests\BililiveRecorder.Flv.Tests.csproj", "{32E554B1-0ECC-4145-85B8-3FC128FEBEA1}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BililiveRecorder.Flv.Tests", "test\BililiveRecorder.Flv.Tests\BililiveRecorder.Flv.Tests.csproj", "{32E554B1-0ECC-4145-85B8-3FC128FEBEA1}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BililiveRecorder.Web", "BililiveRecorder.Web\BililiveRecorder.Web.csproj", "{263EC584-AFD5-45C9-8347-127016B3B8F5}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BililiveRecorder.Web.Schemas", "BililiveRecorder.Web.Schemas\BililiveRecorder.Web.Schemas.csproj", "{4E72646D-8E25-49E5-B72C-E9749141DBF4}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
@ -63,6 +67,14 @@ Global
|
||||||
{32E554B1-0ECC-4145-85B8-3FC128FEBEA1}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{32E554B1-0ECC-4145-85B8-3FC128FEBEA1}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{32E554B1-0ECC-4145-85B8-3FC128FEBEA1}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{32E554B1-0ECC-4145-85B8-3FC128FEBEA1}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{32E554B1-0ECC-4145-85B8-3FC128FEBEA1}.Release|Any CPU.Build.0 = Release|Any CPU
|
{32E554B1-0ECC-4145-85B8-3FC128FEBEA1}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{263EC584-AFD5-45C9-8347-127016B3B8F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{263EC584-AFD5-45C9-8347-127016B3B8F5}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{263EC584-AFD5-45C9-8347-127016B3B8F5}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{263EC584-AFD5-45C9-8347-127016B3B8F5}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{4E72646D-8E25-49E5-B72C-E9749141DBF4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{4E72646D-8E25-49E5-B72C-E9749141DBF4}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{4E72646D-8E25-49E5-B72C-E9749141DBF4}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{4E72646D-8E25-49E5-B72C-E9749141DBF4}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
@ -75,12 +87,14 @@ Global
|
||||||
{521EC763-5694-45A8-B87F-6E6B7F2A3BD4} = {623A2ACC-DAC6-4E6F-9242-B4B54381AAE1}
|
{521EC763-5694-45A8-B87F-6E6B7F2A3BD4} = {623A2ACC-DAC6-4E6F-9242-B4B54381AAE1}
|
||||||
{4FAAE8E7-AC4E-4E99-A7D1-53D20AD8A200} = {2D44A59D-E437-4FEE-8A2E-3FF00D53A64D}
|
{4FAAE8E7-AC4E-4E99-A7D1-53D20AD8A200} = {2D44A59D-E437-4FEE-8A2E-3FF00D53A64D}
|
||||||
{32E554B1-0ECC-4145-85B8-3FC128FEBEA1} = {623A2ACC-DAC6-4E6F-9242-B4B54381AAE1}
|
{32E554B1-0ECC-4145-85B8-3FC128FEBEA1} = {623A2ACC-DAC6-4E6F-9242-B4B54381AAE1}
|
||||||
|
{263EC584-AFD5-45C9-8347-127016B3B8F5} = {2D44A59D-E437-4FEE-8A2E-3FF00D53A64D}
|
||||||
|
{4E72646D-8E25-49E5-B72C-E9749141DBF4} = {2D44A59D-E437-4FEE-8A2E-3FF00D53A64D}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
RESX_SortFileContentOnSave = True
|
|
||||||
SolutionGuid = {F3CB8B14-077A-458F-BD8E-1747ED0F5170}
|
|
||||||
RESX_NeutralResourcesLanguage = zh-Hans
|
|
||||||
RESX_SaveFilesImmediatelyUponChange = True
|
|
||||||
RESX_ShowErrorsInErrorList = False
|
RESX_ShowErrorsInErrorList = False
|
||||||
|
RESX_SaveFilesImmediatelyUponChange = True
|
||||||
|
RESX_NeutralResourcesLanguage = zh-Hans
|
||||||
|
SolutionGuid = {F3CB8B14-077A-458F-BD8E-1747ED0F5170}
|
||||||
|
RESX_SortFileContentOnSave = True
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
|
|
@ -7,7 +7,7 @@ export const data: Array<ConfigEntry> = [
|
||||||
type: "int",
|
type: "int",
|
||||||
configType: "roomOnly",
|
configType: "roomOnly",
|
||||||
defaultValue: "default",
|
defaultValue: "default",
|
||||||
// web_readonly: true,
|
webReadonly: true,
|
||||||
markdown: ""
|
markdown: ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -33,7 +33,7 @@ const map: SectionInfoMap = {
|
||||||
header: true,
|
header: true,
|
||||||
build: builderCli
|
build: builderCli
|
||||||
},
|
},
|
||||||
web_is_disabled_for_now: {
|
web: {
|
||||||
path: './BililiveRecorder.Web.Schemas/Types/Config.gen.cs',
|
path: './BililiveRecorder.Web.Schemas/Types/Config.gen.cs',
|
||||||
format: true,
|
format: true,
|
||||||
header: true,
|
header: true,
|
||||||
|
|
|
@ -1,5 +1,125 @@
|
||||||
import { ConfigEntry, ConfigEntryType } from "../types"
|
import { ConfigEntry, ConfigEntryType } from "../types"
|
||||||
|
import { trimEnd } from "../utils";
|
||||||
|
|
||||||
export default function (data: ConfigEntry[]): string {
|
export default function (data: ConfigEntry[]): string {
|
||||||
return ""
|
let result = `using BililiveRecorder.Core.Config.V2;
|
||||||
|
using GraphQL.Types;
|
||||||
|
using HierarchicalPropertyDefault;
|
||||||
|
#nullable enable
|
||||||
|
namespace BililiveRecorder.Web.Schemas.Types
|
||||||
|
{
|
||||||
|
`;
|
||||||
|
function write_query_graphType_property(r: ConfigEntry) {
|
||||||
|
if (r.configType == "roomOnly") {
|
||||||
|
result += `this.Field(x => x.${r.name});\n`;
|
||||||
|
} else {
|
||||||
|
result += `this.Field(x => x.Optional${r.name}, type: typeof(HierarchicalOptionalType<${trimEnd(r.type, '?')}>));\n`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function write_mutation_graphType_property(r: ConfigEntry) {
|
||||||
|
if (r.configType == "roomOnly") {
|
||||||
|
result += `this.Field(x => x.${r.name}, nullable: true);\n`;
|
||||||
|
} else {
|
||||||
|
result += `this.Field(x => x.Optional${r.name}, nullable: true, type: typeof(HierarchicalOptionalInputType<${trimEnd(r.type, '?')}>));\n`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function write_mutation_dataType_property(r: ConfigEntry) {
|
||||||
|
if (r.configType == "roomOnly") {
|
||||||
|
result += `public ${r.type}? ${r.name} { get; set; }\n`;
|
||||||
|
} else {
|
||||||
|
result += `public Optional<${r.type}>? Optional${r.name} { get; set; }\n`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function write_mutation_apply_method(r: ConfigEntry) {
|
||||||
|
if (r.configType == "roomOnly") {
|
||||||
|
result += `if (this.${r.name}.HasValue) config.${r.name} = this.${r.name}.Value;\n`;
|
||||||
|
} else {
|
||||||
|
result += `if (this.Optional${r.name}.HasValue) config.Optional${r.name} = this.Optional${r.name}.Value;\n`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{ // ====== RoomConfigType ======
|
||||||
|
result += "internal class RoomConfigType : ObjectGraphType<RoomConfig>\n{\n";
|
||||||
|
result += "public RoomConfigType()\n{\n"
|
||||||
|
|
||||||
|
data.filter(r => r.configType != "globalOnly").forEach(r => write_query_graphType_property(r));
|
||||||
|
|
||||||
|
result += "}\n}\n\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
{ // ====== GlobalConfigType ======
|
||||||
|
result += "internal class GlobalConfigType : ObjectGraphType<GlobalConfig>\n{\n"
|
||||||
|
result += "public GlobalConfigType()\n{\n";
|
||||||
|
|
||||||
|
data.filter(r => r.configType != "roomOnly")
|
||||||
|
.forEach(r => write_query_graphType_property(r));
|
||||||
|
|
||||||
|
result += "}\n}\n\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
{ // ====== DefaultConfigType ======
|
||||||
|
result += "internal class DefaultConfigType : ObjectGraphType<DefaultConfig>\n{\n"
|
||||||
|
result += "public DefaultConfigType()\n{\n";
|
||||||
|
|
||||||
|
data.filter(r => r.configType != "roomOnly")
|
||||||
|
.forEach(r => {
|
||||||
|
result += `this.Field(x => x.${r.name});\n`;
|
||||||
|
});
|
||||||
|
|
||||||
|
result += "}\n}\n\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
{ // ====== SetRoomConfig ======
|
||||||
|
result += "internal class SetRoomConfig\n{\n"
|
||||||
|
|
||||||
|
data.filter(x => x.configType != "globalOnly" && !x.webReadonly)
|
||||||
|
.forEach(r => write_mutation_dataType_property(r));
|
||||||
|
|
||||||
|
result += "\npublic void ApplyTo(RoomConfig config)\n{\n";
|
||||||
|
|
||||||
|
data.filter(x => x.configType != "globalOnly" && !x.webReadonly)
|
||||||
|
.forEach(r => write_mutation_apply_method(r));
|
||||||
|
|
||||||
|
result += "}\n}\n\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
{ // ====== SetRoomConfigType ======
|
||||||
|
result += "internal class SetRoomConfigType : InputObjectGraphType<SetRoomConfig>\n{\n"
|
||||||
|
result += "public SetRoomConfigType()\n{\n";
|
||||||
|
|
||||||
|
data.filter(x => x.configType != "globalOnly" && !x.webReadonly)
|
||||||
|
.forEach(r => write_mutation_graphType_property(r));
|
||||||
|
|
||||||
|
result += "}\n}\n\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
{ // ====== SetGlobalConfig ======
|
||||||
|
result += "internal class SetGlobalConfig\n{\n"
|
||||||
|
|
||||||
|
data.filter(r => r.configType != "roomOnly" && !r.webReadonly)
|
||||||
|
.forEach(r => write_mutation_dataType_property(r));
|
||||||
|
|
||||||
|
result += "\npublic void ApplyTo(GlobalConfig config)\n{\n";
|
||||||
|
|
||||||
|
data.filter(r => r.configType != "roomOnly" && !r.webReadonly)
|
||||||
|
.forEach(r => write_mutation_apply_method(r));
|
||||||
|
|
||||||
|
result += "}\n}\n\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
{ // ====== SetGlobalConfigType ======
|
||||||
|
result += "internal class SetGlobalConfigType : InputObjectGraphType<SetGlobalConfig>\n{\n"
|
||||||
|
result += "public SetGlobalConfigType()\n{\n";
|
||||||
|
|
||||||
|
data.filter(r => r.configType != "roomOnly" && !r.webReadonly)
|
||||||
|
.forEach(r => write_mutation_graphType_property(r));
|
||||||
|
|
||||||
|
result += "}\n}\n\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
result += `}\n`;
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,2 @@
|
||||||
import { ConfigEntry } from "../types"
|
|
||||||
export { default as code } from "./code"
|
export { default as code } from "./code"
|
||||||
export { default as doc } from "./doc"
|
export { default as doc } from "./doc"
|
||||||
|
|
||||||
export const core = function (data: Array<ConfigEntry>) {
|
|
||||||
}
|
|
||||||
export const cli = function (data: Array<ConfigEntry>) {
|
|
||||||
}
|
|
||||||
export const web = function (data: Array<ConfigEntry>) {
|
|
||||||
}
|
|
||||||
export const schema = function (data: Array<ConfigEntry>) {
|
|
||||||
}
|
|
|
@ -1,14 +1,5 @@
|
||||||
import { spawn } from "child_process";
|
|
||||||
import { stdout, stderr } from "process";
|
|
||||||
import { writeFileSync } from "fs";
|
|
||||||
import { resolve, dirname } from "path";
|
|
||||||
import { fileURLToPath } from 'url';
|
|
||||||
|
|
||||||
import { data } from "./data"
|
|
||||||
import * as generators from "./generators"
|
import * as generators from "./generators"
|
||||||
|
|
||||||
const baseDirectory = __dirname
|
|
||||||
|
|
||||||
const argv = process.argv.slice(2)
|
const argv = process.argv.slice(2)
|
||||||
|
|
||||||
switch (argv[0]) {
|
switch (argv[0]) {
|
||||||
|
|
14
config_gen/package-lock.json
generated
14
config_gen/package-lock.json
generated
|
@ -5,7 +5,7 @@
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^16.6.1",
|
"@types/node": "^16.11.26",
|
||||||
"ts-node": "^10.2.0",
|
"ts-node": "^10.2.0",
|
||||||
"tslib": "^2.3.1",
|
"tslib": "^2.3.1",
|
||||||
"typescript": "^4.3.5"
|
"typescript": "^4.3.5"
|
||||||
|
@ -57,9 +57,9 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "16.6.1",
|
"version": "16.11.26",
|
||||||
"resolved": "https://registry.nlark.com/@types/node/download/@types/node-16.6.1.tgz?cache=0&sync_timestamp=1628800447602&other_urls=https%3A%2F%2Fregistry.nlark.com%2F%40types%2Fnode%2Fdownload%2F%40types%2Fnode-16.6.1.tgz",
|
"resolved": "https://registry.npmmirror.com/@types/node/-/node-16.11.26.tgz",
|
||||||
"integrity": "sha1-ruYse5ZvVfxmx7bfodWNsqYW2mE=",
|
"integrity": "sha512-GZ7bu5A6+4DtG7q9GsoHXy3ALcgeIHP4NnL0Vv2wu0uUB/yQex26v0tf6/na1mm0+bS9Uw+0DFex7aaKr2qawQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/acorn": {
|
"node_modules/acorn": {
|
||||||
|
@ -224,9 +224,9 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@types/node": {
|
"@types/node": {
|
||||||
"version": "16.6.1",
|
"version": "16.11.26",
|
||||||
"resolved": "https://registry.nlark.com/@types/node/download/@types/node-16.6.1.tgz?cache=0&sync_timestamp=1628800447602&other_urls=https%3A%2F%2Fregistry.nlark.com%2F%40types%2Fnode%2Fdownload%2F%40types%2Fnode-16.6.1.tgz",
|
"resolved": "https://registry.npmmirror.com/@types/node/-/node-16.11.26.tgz",
|
||||||
"integrity": "sha1-ruYse5ZvVfxmx7bfodWNsqYW2mE=",
|
"integrity": "sha512-GZ7bu5A6+4DtG7q9GsoHXy3ALcgeIHP4NnL0Vv2wu0uUB/yQex26v0tf6/na1mm0+bS9Uw+0DFex7aaKr2qawQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"acorn": {
|
"acorn": {
|
||||||
|
|
|
@ -3,10 +3,9 @@
|
||||||
"build": "ts-node index.ts"
|
"build": "ts-node index.ts"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^16.6.1",
|
"@types/node": "^16.11.26",
|
||||||
"ts-node": "^10.2.0",
|
"ts-node": "^10.2.0",
|
||||||
"tslib": "^2.3.1",
|
"tslib": "^2.3.1",
|
||||||
"typescript": "^4.3.5"
|
"typescript": "^4.3.5"
|
||||||
},
|
}
|
||||||
"dependencies": {}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,8 @@ export interface ConfigEntry {
|
||||||
readonly type: string,
|
readonly type: string,
|
||||||
/** 设置类型 */
|
/** 设置类型 */
|
||||||
readonly configType: ConfigEntryType
|
readonly configType: ConfigEntryType
|
||||||
|
/** Web API 只读属性 */
|
||||||
|
readonly webReadonly?: boolean,
|
||||||
/** 是否为高级设置(隐藏设置),默认为 false */
|
/** 是否为高级设置(隐藏设置),默认为 false */
|
||||||
readonly advancedConfig?: boolean,
|
readonly advancedConfig?: boolean,
|
||||||
/** 默认值 */
|
/** 默认值 */
|
||||||
|
|
Loading…
Reference in New Issue
Block a user