diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json
new file mode 100644
index 0000000..c31beb8
--- /dev/null
+++ b/.config/dotnet-tools.json
@@ -0,0 +1,12 @@
+{
+ "version": 1,
+ "isRoot": true,
+ "tools": {
+ "dotnet-format": {
+ "version": "5.1.225507",
+ "commands": [
+ "dotnet-format"
+ ]
+ }
+ }
+}
\ No newline at end of file
diff --git a/.tools/build_config.js b/.tools/build_config.js
new file mode 100644
index 0000000..9700694
--- /dev/null
+++ b/.tools/build_config.js
@@ -0,0 +1,81 @@
+"use strict";
+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 "./config_data.js"
+
+import generate_json_schema from "./generate_json_schema.js"
+import generate_core_config from "./generate_core_config.js"
+import generate_web_config from "./generate_web_config.js"
+
+const baseDirectory = dirname(fileURLToPath(import.meta.url));
+
+const DO_NOT_EDIT_COMMENT = `// ******************************
+// GENERATED CODE, DO NOT EDIT MANUALLY.
+// SEE .tools/build_config.js
+// ******************************\n\n`
+
+// ---------------------------------------------
+// SCHEMA
+// ---------------------------------------------
+
+console.log("[node] writing json schema...")
+
+const json_schema_path = resolve(baseDirectory, '../configV2.schema.json');
+
+const json_schema_code = generate_json_schema(data);
+
+writeFileSync(json_schema_path, json_schema_code, {
+ encoding: "utf8"
+});
+
+// ---------------------------------------------
+// CORE
+// ---------------------------------------------
+
+console.log("[node] writing core config...")
+
+const core_config_path = resolve(baseDirectory, '../BililiveRecorder.Core/Config/V2/Config.gen.cs');
+
+const core_config_code = generate_core_config(data);
+
+writeFileSync(core_config_path, DO_NOT_EDIT_COMMENT + core_config_code, {
+ encoding: "utf8"
+});
+
+// ---------------------------------------------
+// WEB
+// ---------------------------------------------
+/* disabled
+console.log("[node] writing web config...")
+
+const web_config_path = resolve(baseDirectory, '../BililiveRecorder.Web.Schemas/Types/Config.gen.cs');
+
+const web_config_code = generate_web_config(data);
+
+writeFileSync(web_config_path, DO_NOT_EDIT_COMMENT + web_config_code, {
+ encoding: "utf8"
+});
+*/
+// ---------------------------------------------
+// FORMAT
+// ---------------------------------------------
+
+console.log("[node] formatting...")
+
+let format = spawn('dotnet', ['tool', 'run', 'dotnet-format', '--', '--include', './BililiveRecorder.Core/Config/V2/Config.gen.cs'/*, './BililiveRecorder.Web.Schemas/Types/Config.gen.cs'*/])
+
+format.stdout.on('data', function (data) {
+ stdout.write('[dotnet-format] ' + data.toString());
+});
+
+format.stderr.on('data', function (data) {
+ stderr.write('[dotnet-format] ' + data.toString());
+});
+
+format.on('exit', function (code) {
+ console.log('[node] format done code ' + code.toString());
+});
diff --git a/BililiveRecorder.Core/Config/V2/build_config.data.js b/.tools/config_data.js
similarity index 97%
rename from BililiveRecorder.Core/Config/V2/build_config.data.js
rename to .tools/config_data.js
index 5ec9813..e41ee64 100644
--- a/BililiveRecorder.Core/Config/V2/build_config.data.js
+++ b/.tools/config_data.js
@@ -1,4 +1,4 @@
-module.exports = {
+export default {
"global": [{
"name": "TimingStreamRetry",
"type": "uint",
@@ -70,13 +70,14 @@ module.exports = {
"type": "int",
"desc": "房间号",
"default": "default",
- "without_global": true
+ "without_global": true,
+ "web_readonly": true
}, {
"name": "AutoRecord",
"type": "bool",
"desc": "是否启用自动录制",
"default": "default",
- "without_global": true
+ "without_global": true,
}, {
"name": "RecordMode",
"type": "RecordMode",
diff --git a/.tools/generate_core_config.js b/.tools/generate_core_config.js
new file mode 100644
index 0000000..2f3a59f
--- /dev/null
+++ b/.tools/generate_core_config.js
@@ -0,0 +1,66 @@
+"use strict";
+
+export default function generate_core_config(data) {
+ let result = `using System.ComponentModel;
+using HierarchicalPropertyDefault;
+using Newtonsoft.Json;
+
+#nullable enable
+namespace BililiveRecorder.Core.Config.V2
+{
+`;
+
+ function write_property(r) {
+ result += `/// \n/// ${r.desc}\n/// \n`;
+ result += `public ${r.type}${!!r.nullable ? "?" : ""} ${r.name} { get => this.GetPropertyValue<${r.type}>(); set => this.SetPropertyValue(value); }\n`;
+ result += `public bool Has${r.name} { get => this.GetPropertyHasValue(nameof(this.${r.name})); set => this.SetPropertyHasValue<${r.type}>(value, nameof(this.${r.name})); }\n`;
+ result += `[JsonProperty(nameof(${r.name})), EditorBrowsable(EditorBrowsableState.Never)]\n`;
+ result += `public Optional<${r.type}${!!r.nullable ? "?" : ""}> Optional${r.name} { get => this.GetPropertyValueOptional<${r.type}>(nameof(this.${r.name})); set => this.SetPropertyValueOptional(value, nameof(this.${r.name})); }\n\n`;
+ }
+
+ function write_readonly_property(r) {
+ result += `/// \n/// ${r.desc}\n/// \n`;
+ result += `public ${r.type}${!!r.nullable ? "?" : ""} ${r.name} => this.GetPropertyValue<${r.type}>();\n\n`;
+ }
+
+ {
+ result += "[JsonObject(MemberSerialization.OptIn)]\n";
+ result += "public sealed partial class RoomConfig : HierarchicalObject\n";
+ result += "{\n";
+
+ data.room.forEach(r => write_property(r));
+ data.global.forEach(r => write_readonly_property(r));
+
+ result += "}\n\n";
+ }
+
+ {
+ result += "[JsonObject(MemberSerialization.OptIn)]\n";
+ result += "public sealed partial class GlobalConfig : HierarchicalObject\n";
+ result += "{\n";
+
+ data.global
+ .concat(data.room.filter(x => !x.without_global))
+ .forEach(r => write_property(r));
+
+ result += "}\n\n";
+ }
+
+ {
+ result += `public sealed partial class DefaultConfig
+ {
+ public static readonly DefaultConfig Instance = new DefaultConfig();
+ private DefaultConfig() {}\n\n`;
+
+ data.global
+ .concat(data.room.filter(x => !x.without_global))
+ .forEach(r => {
+ result += `public ${r.type} ${r.name} => ${r.default};\n\n`;
+ });
+
+ result += "}\n\n";
+ }
+
+ result += `}\n`;
+ return result;
+}
diff --git a/.tools/generate_json_schema.js b/.tools/generate_json_schema.js
new file mode 100644
index 0000000..49ab24e
--- /dev/null
+++ b/.tools/generate_json_schema.js
@@ -0,0 +1,95 @@
+function tryEvalValue(str) {
+ try {
+ return eval(str);
+ } catch {
+ return str;
+ }
+}
+
+function mapTypeToJsonSchema(name, type, defVal) {
+ switch (type) {
+ case "RecordMode":
+ return { type: "integer", default: 0, enum: [0, 1], "description": "0: Standard\n1: Raw" };
+ case "CuttingMode":
+ return { type: "integer", default: 0, enum: [0, 1, 2], "description": "0: 禁用\n1: 根据时间切割\n2: 根据文件大小切割" };
+ case "uint":
+ return { type: "integer", minimum: 0, maximum: 4294967295, default: tryEvalValue(defVal) };
+ case "int":
+ return { type: "integer", minimum: -2147483648, maximum: 2147483647, default: tryEvalValue(defVal) };
+ case "bool":
+ return { type: "boolean", default: tryEvalValue(defVal) };
+ case "string":
+ if (name === 'Cookie') {
+ return { type: "string", pattern: "^(\S+=\S+;? ?)*$", maxLength: 4096, };
+ }
+ return { type: "string", default: defVal === 'string.Empty' ? '' : tryEvalValue(defVal.replace(/^@/, '')) };
+ default:
+ return { type, default: defVal };
+ }
+}
+
+function insert(target, { name, type, desc, default: defVal/*, nullable */ }) {
+ const typeObj = mapTypeToJsonSchema(name, type, defVal);
+ if (defVal === 'default') delete typeObj['default'];
+ target[name] = {
+ "description": desc,
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "HasValue": { "type": "boolean", "default": true }, "Value": typeObj
+ }
+ };
+}
+
+export default function generate_json_schema(data) {
+ const sharedConfig = {};
+ const globalConfig = {};
+ const roomConfig = {};
+
+ data.room.filter(x => !x.without_global).forEach(v => insert(sharedConfig, v));
+ data.room.filter(x => x.without_global).forEach(v => insert(roomConfig, v));
+ data.global.forEach(v => insert(globalConfig, v));
+
+ const schema = {
+ "$comment": "GENERATED CODE, DO NOT EDIT MANUALLY.",
+ "$schema": "http://json-schema.org/schema",
+ "definitions": {
+ "global-config": {
+ "description": "全局配置",
+ "additionalProperties": false,
+ "properties": { ...globalConfig, ...sharedConfig }
+ },
+ "room-config": {
+ "description": "单个房间配置",
+ "additionalProperties": false,
+ "properties": { ...roomConfig, ...sharedConfig }
+ }
+ },
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "$schema",
+ "version"
+ ],
+ "properties": {
+ "$schema": {
+ "type": "string",
+ "default": "https://raw.githubusercontent.com/Bililive/BililiveRecorder/dev-1.3/configV2.schema.json"
+ },
+ "version": {
+ "const": 2
+ },
+ "global": {
+ "$ref": "#/definitions/global-config"
+ },
+ "rooms": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/room-config"
+ }
+ }
+ }
+ }
+
+ return JSON.stringify(schema, null, 2)
+}
diff --git a/.tools/generate_web_config.js b/.tools/generate_web_config.js
new file mode 100644
index 0000000..25dc211
--- /dev/null
+++ b/.tools/generate_web_config.js
@@ -0,0 +1,134 @@
+"use strict";
+
+export default function generate_web_config(data) {
+ 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) {
+ if (r.without_global) {
+ result += `this.Field(x => x.${r.name});\n`;
+ } else {
+ result += `this.Field(x => x.Optional${r.name}, type: typeof(HierarchicalOptionalType<${r.type}>));\n`;
+ }
+ }
+
+ function write_mutation_graphType_property(r) {
+ if (r.without_global) {
+ result += `this.Field(x => x.${r.name}, nullable: true);\n`;
+ } else {
+ result += `this.Field(x => x.Optional${r.name}, nullable: true, type: typeof(HierarchicalOptionalInputType<${r.type}>));\n`;
+ }
+ }
+
+ function write_mutation_dataType_property(r) {
+ if (r.without_global) {
+ result += `public ${r.type + (r.nullable ? '?' : '')}? ${r.name} { get; set; }\n`;
+ } else {
+ result += `public Optional<${r.type + (r.nullable ? '?' : '')}>? Optional${r.name} { get; set; }\n`;
+ }
+ }
+
+ function write_mutation_apply_method(r) {
+ if (r.without_global) {
+ 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\n{\n";
+ result += "public RoomConfigType()\n{\n"
+
+ data.room.forEach(r => write_query_graphType_property(r));
+
+ result += "}\n}\n\n";
+ }
+
+ { // ====== GlobalConfigType ======
+ result += "internal class GlobalConfigType : ObjectGraphType\n{\n"
+ result += "public GlobalConfigType()\n{\n";
+
+ data.global
+ .concat(data.room.filter(x => !x.without_global))
+ .forEach(r => write_query_graphType_property(r));
+
+ result += "}\n}\n\n";
+ }
+
+ { // ====== DefaultConfigType ======
+ result += "internal class DefaultConfigType : ObjectGraphType\n{\n"
+ result += "public DefaultConfigType()\n{\n";
+
+ data.global
+ .concat(data.room.filter(x => !x.without_global))
+ .forEach(r => {
+ result += `this.Field(x => x.${r.name});\n`;
+ });
+
+ result += "}\n}\n\n";
+ }
+
+ { // ====== SetRoomConfig ======
+ result += "internal class SetRoomConfig\n{\n"
+
+ data.room.filter(x => !x.web_readonly)
+ .forEach(r => write_mutation_dataType_property(r));
+
+ result += "\npublic void ApplyTo(RoomConfig config)\n{\n";
+
+ data.room.filter(x => !x.web_readonly)
+ .forEach(r => write_mutation_apply_method(r));
+
+ result += "}\n}\n\n";
+ }
+
+ { // ====== SetRoomConfigType ======
+ result += "internal class SetRoomConfigType : InputObjectGraphType\n{\n"
+ result += "public SetRoomConfigType()\n{\n";
+
+ data.room.filter(x => !x.web_readonly)
+ .forEach(r => write_mutation_graphType_property(r));
+
+ result += "}\n}\n\n";
+ }
+
+ { // ====== SetGlobalConfig ======
+ result += "internal class SetGlobalConfig\n{\n"
+
+ data.global
+ .concat(data.room.filter(x => !x.without_global))
+ .filter(x => !x.web_readonly)
+ .forEach(r => write_mutation_dataType_property(r));
+
+ result += "\npublic void ApplyTo(GlobalConfig config)\n{\n";
+
+ data.global
+ .concat(data.room.filter(x => !x.without_global))
+ .filter(x => !x.web_readonly)
+ .forEach(r => write_mutation_apply_method(r));
+
+ result += "}\n}\n\n";
+ }
+
+ { // ====== SetGlobalConfigType ======
+ result += "internal class SetGlobalConfigType : InputObjectGraphType\n{\n"
+ result += "public SetGlobalConfigType()\n{\n";
+
+ data.global
+ .concat(data.room.filter(x => !x.without_global))
+ .filter(x => !x.web_readonly)
+ .forEach(r => write_mutation_graphType_property(r));
+
+ result += "}\n}\n\n";
+ }
+
+ result += `}\n`;
+ return result;
+}
diff --git a/.tools/package.json b/.tools/package.json
new file mode 100644
index 0000000..96ae6e5
--- /dev/null
+++ b/.tools/package.json
@@ -0,0 +1,3 @@
+{
+ "type": "module"
+}
\ No newline at end of file
diff --git a/BililiveRecorder.Cli/Configure/ConfigureCommand.cs b/BililiveRecorder.Cli/Configure/ConfigureCommand.cs
index 723c641..436fa4f 100644
--- a/BililiveRecorder.Cli/Configure/ConfigureCommand.cs
+++ b/BililiveRecorder.Cli/Configure/ConfigureCommand.cs
@@ -135,7 +135,7 @@ namespace BililiveRecorder.Cli.Configure
switch (selection)
{
case JsonSchemaSelection.Default:
- config.DollarSignSchema = "https://raw.githubusercontent.com/Bililive/BililiveRecorder/dev-1.3/BililiveRecorder.Core/Config/V2/config.schema.json";
+ config.DollarSignSchema = "https://raw.githubusercontent.com/Bililive/BililiveRecorder/dev-1.3/configV2.schema.json";
break;
case JsonSchemaSelection.Custom:
config.DollarSignSchema = AnsiConsole.Prompt(new TextPrompt("[green]JSON Schema[/]:").AllowEmpty());
diff --git a/BililiveRecorder.Cli/Configure/JsonSchemaSelection.cs b/BililiveRecorder.Cli/Configure/JsonSchemaSelection.cs
index a16f975..bc90b83 100644
--- a/BililiveRecorder.Cli/Configure/JsonSchemaSelection.cs
+++ b/BililiveRecorder.Cli/Configure/JsonSchemaSelection.cs
@@ -4,7 +4,7 @@ namespace BililiveRecorder.Cli.Configure
{
public enum JsonSchemaSelection
{
- [Description("https://raw.githubusercontent.com/.../config.schema.json")]
+ [Description("https://raw.githubusercontent.com/Bililive/BililiveRecorder/dev-1.3/configV2.schema.json")]
Default,
[Description("Custom")]
diff --git a/BililiveRecorder.Core/Config/V2/Config.gen.cs b/BililiveRecorder.Core/Config/V2/Config.gen.cs
index 513fbc7..f33bb79 100644
--- a/BililiveRecorder.Core/Config/V2/Config.gen.cs
+++ b/BililiveRecorder.Core/Config/V2/Config.gen.cs
@@ -1,7 +1,8 @@
// ******************************
-// GENERATED CODE, DO NOT EDIT.
-// RUN FORMATTER AFTER GENERATE
+// GENERATED CODE, DO NOT EDIT MANUALLY.
+// SEE .tools/build_config.js
// ******************************
+
using System.ComponentModel;
using HierarchicalPropertyDefault;
using Newtonsoft.Json;
@@ -337,7 +338,7 @@ namespace BililiveRecorder.Core.Config.V2
public sealed partial class DefaultConfig
{
- internal static readonly DefaultConfig Instance = new DefaultConfig();
+ public static readonly DefaultConfig Instance = new DefaultConfig();
private DefaultConfig() { }
public uint TimingStreamRetry => 6 * 1000;
diff --git a/BililiveRecorder.Core/Config/V2/build_config.js b/BililiveRecorder.Core/Config/V2/build_config.js
deleted file mode 100644
index 86a9755..0000000
--- a/BililiveRecorder.Core/Config/V2/build_config.js
+++ /dev/null
@@ -1,169 +0,0 @@
-"use strict";
-const fs = require("fs");
-const data = require("./build_config.data.js");
-
-const CODE_HEADER =
- `// ******************************
-// GENERATED CODE, DO NOT EDIT.
-// RUN FORMATTER AFTER GENERATE
-// ******************************
-using System.ComponentModel;
-using HierarchicalPropertyDefault;
-using Newtonsoft.Json;
-
-#nullable enable
-namespace BililiveRecorder.Core.Config.V2
-{
-`;
-
-const CODE_FOOTER = `}\n`;
-
-let result = CODE_HEADER;
-
-function write_property(r) {
- result += `/// \n/// ${r.desc}\n/// \n`
- result += `public ${r.type}${!!r.nullable ? "?" : ""} ${r.name} { get => this.GetPropertyValue<${r.type}>(); set => this.SetPropertyValue(value); }\n`
- result += `public bool Has${r.name} { get => this.GetPropertyHasValue(nameof(this.${r.name})); set => this.SetPropertyHasValue<${r.type}>(value, nameof(this.${r.name})); }\n`
- result += `[JsonProperty(nameof(${r.name})), EditorBrowsable(EditorBrowsableState.Never)]\n`
- result += `public Optional<${r.type}${!!r.nullable ? "?" : ""}> Optional${r.name} { get => this.GetPropertyValueOptional<${r.type}>(nameof(this.${r.name})); set => this.SetPropertyValueOptional(value, nameof(this.${r.name})); }\n\n`
-}
-
-function write_readonly_property(r) {
- result += `/// \n/// ${r.desc}\n/// \n`
- result += `public ${r.type}${!!r.nullable ? "?" : ""} ${r.name} => this.GetPropertyValue<${r.type}>();\n\n`
-}
-
-{
- result += "[JsonObject(MemberSerialization.OptIn)]\n"
- result += "public sealed partial class RoomConfig : HierarchicalObject\n"
- result += "{\n";
-
- data.room.forEach(r => write_property(r))
- data.global.forEach(r => write_readonly_property(r))
-
- result += "}\n\n"
-}
-
-{
- result += "[JsonObject(MemberSerialization.OptIn)]\n"
- result += "public sealed partial class GlobalConfig : HierarchicalObject\n"
- result += "{\n";
-
- data.global
- .concat(data.room.filter(x => !x.without_global))
- .forEach(r => write_property(r))
-
- result += "}\n\n"
-}
-
-{
- result += `public sealed partial class DefaultConfig
- {
- internal static readonly DefaultConfig Instance = new DefaultConfig();
- private DefaultConfig() {}\n\n`;
-
- data.global
- .concat(data.room.filter(x => !x.without_global))
- .forEach(r => {
- result += `public ${r.type} ${r.name} => ${r.default};\n\n`
- })
-
- result += "}\n\n"
-}
-
-result += CODE_FOOTER;
-
-fs.writeFileSync("./Config.gen.cs", result, {
- encoding: "utf8"
-});
-
-console.log("记得 format Config.gen.cs")
-
-/** 进行一个json schema的生成 */
-const sharedConfig = {};
-const globalConfig = {};
-const roomConfig = {};
-function tEval(str) {
- try {
- return eval(str);
- } catch {
- return str;
- }
-}
-function switchType(name, type, defVal) {
- switch (type) {
- case "RecordMode":
- return { type: "integer", default: 0, enum: [0, 1], "description": "0: Standard\n1: Raw" };
- case "CuttingMode":
- return { type: "integer", default: 0, enum: [0, 1, 2], "description": "0: 禁用\n1: 根据时间切割\n2: 根据文件大小切割" };
- case "uint":
- return { type: "integer", minimum: 0, maximum: 4294967295, default: tEval(defVal) };
- case "int":
- return { type: "integer", minimum: -2147483648, maximum: 2147483647, default: tEval(defVal) };
- case "bool":
- return { type: "boolean", default: tEval(defVal) };
- case "string":
- if (name === 'Cookie') {
- return { type: "string", pattern: "^(\S+=\S+;? ?)*$", maxLength: 4096, };
- }
- return { type: "string", default: defVal === 'string.Empty' ? '' : tEval(defVal.replace(/^@/, '')) };
- default:
- return { type, default: defVal };
- }
-}
-function insert(target, { name, type, desc, default: defVal/*, nullable */ }) {
- const typeObj = switchType(name, type, defVal);
- if (defVal === 'default') delete typeObj['default'];
- target[name] = {
- "description": desc,
- "type": "object",
- "additionalProperties": false,
- "properties": {
- "HasValue": { "type": "boolean", "default": true }, "Value": typeObj
- }
- };
-}
-data.room.filter(x => !x.without_global).forEach(v => insert(sharedConfig, v));
-data.room.filter(x => x.without_global).forEach(v => insert(roomConfig, v));
-data.global.forEach(v => insert(globalConfig, v));
-
-
-fs.writeFileSync("./config.schema.json", "// GENERATED CODE, DO NOT EDIT.\n" + JSON.stringify({
- "$schema": "http://json-schema.org/schema",
- "definitions": {
- "global-config": {
- "description": "全局配置",
- "additionalProperties": false,
- "properties": { ...sharedConfig, ...globalConfig }
- },
- "room-config": {
- "description": "单个房间配置",
- "additionalProperties": false,
- "properties": { ...sharedConfig, ...roomConfig }
- }
- },
- "type": "object",
- "additionalProperties": false,
- "required": [
- "$schema",
- "version"
- ],
- "properties": {
- "$schema": {
- "type": "string",
- "default": "https://raw.githubusercontent.com/Bililive/BililiveRecorder/dev-1.3/BililiveRecorder.Core/Config/V2/config.schema.json"
- },
- "version": {
- "const": 2
- },
- "global": {
- "$ref": "#/definitions/global-config"
- },
- "rooms": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/room-config"
- }
- }
- }
-}, null, 4));
diff --git a/BililiveRecorder.Core/Config/V2/config.schema.json b/BililiveRecorder.Core/Config/V2/config.schema.json
index 9184a65..23c76fb 100644
--- a/BililiveRecorder.Core/Config/V2/config.schema.json
+++ b/BililiveRecorder.Core/Config/V2/config.schema.json
@@ -1,5 +1,5 @@
-// GENERATED CODE, DO NOT EDIT.
{
+ "$comment": "Deprecated. Use https://raw.githubusercontent.com/Bililive/BililiveRecorder/dev-1.3/configV2.schema.json",
"$schema": "http://json-schema.org/schema",
"definitions": {
"global-config": {
@@ -542,8 +542,7 @@
],
"properties": {
"$schema": {
- "type": "string",
- "default": "https://raw.githubusercontent.com/Bililive/BililiveRecorder/dev-1.3/BililiveRecorder.Core/Config/V2/config.schema.json"
+ "const": "https://raw.githubusercontent.com/Bililive/BililiveRecorder/dev-1.3/configV2.schema.json"
},
"version": {
"const": 2
diff --git a/configV2.schema.json b/configV2.schema.json
new file mode 100644
index 0000000..bb058a0
--- /dev/null
+++ b/configV2.schema.json
@@ -0,0 +1,561 @@
+{
+ "$comment": "GENERATED CODE, DO NOT EDIT MANUALLY.",
+ "$schema": "http://json-schema.org/schema",
+ "definitions": {
+ "global-config": {
+ "description": "全局配置",
+ "additionalProperties": false,
+ "properties": {
+ "TimingStreamRetry": {
+ "description": "录制断开重连时间间隔 毫秒",
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "HasValue": {
+ "type": "boolean",
+ "default": true
+ },
+ "Value": {
+ "type": "integer",
+ "minimum": 0,
+ "maximum": 4294967295,
+ "default": 6000
+ }
+ }
+ },
+ "TimingStreamConnect": {
+ "description": "连接直播服务器超时时间 毫秒",
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "HasValue": {
+ "type": "boolean",
+ "default": true
+ },
+ "Value": {
+ "type": "integer",
+ "minimum": 0,
+ "maximum": 4294967295,
+ "default": 5000
+ }
+ }
+ },
+ "TimingDanmakuRetry": {
+ "description": "弹幕服务器重连时间间隔 毫秒",
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "HasValue": {
+ "type": "boolean",
+ "default": true
+ },
+ "Value": {
+ "type": "integer",
+ "minimum": 0,
+ "maximum": 4294967295,
+ "default": 15000
+ }
+ }
+ },
+ "TimingCheckInterval": {
+ "description": "HTTP API 检查时间间隔 秒",
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "HasValue": {
+ "type": "boolean",
+ "default": true
+ },
+ "Value": {
+ "type": "integer",
+ "minimum": 0,
+ "maximum": 4294967295,
+ "default": 600
+ }
+ }
+ },
+ "TimingWatchdogTimeout": {
+ "description": "最大未收到新直播数据时间 毫秒",
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "HasValue": {
+ "type": "boolean",
+ "default": true
+ },
+ "Value": {
+ "type": "integer",
+ "minimum": 0,
+ "maximum": 4294967295,
+ "default": 10000
+ }
+ }
+ },
+ "RecordDanmakuFlushInterval": {
+ "description": "触发 的弹幕个数",
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "HasValue": {
+ "type": "boolean",
+ "default": true
+ },
+ "Value": {
+ "type": "integer",
+ "minimum": 0,
+ "maximum": 4294967295,
+ "default": 20
+ }
+ }
+ },
+ "Cookie": {
+ "description": "请求 API 时使用的 Cookie",
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "HasValue": {
+ "type": "boolean",
+ "default": true
+ },
+ "Value": {
+ "type": "string",
+ "pattern": "^(S+=S+;? ?)*$",
+ "maxLength": 4096
+ }
+ }
+ },
+ "WebHookUrls": {
+ "description": "录制文件写入结束 Webhook 地址 每行一个",
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "HasValue": {
+ "type": "boolean",
+ "default": true
+ },
+ "Value": {
+ "type": "string",
+ "default": ""
+ }
+ }
+ },
+ "WebHookUrlsV2": {
+ "description": "Webhook v2 地址 每行一个",
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "HasValue": {
+ "type": "boolean",
+ "default": true
+ },
+ "Value": {
+ "type": "string",
+ "default": ""
+ }
+ }
+ },
+ "LiveApiHost": {
+ "description": "替换 api.live.bilibili.com 服务器为其他反代,可以支持在云服务器上录制",
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "HasValue": {
+ "type": "boolean",
+ "default": true
+ },
+ "Value": {
+ "type": "string",
+ "default": "https://api.live.bilibili.com"
+ }
+ }
+ },
+ "RecordFilenameFormat": {
+ "description": "录制文件名模板",
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "HasValue": {
+ "type": "boolean",
+ "default": true
+ },
+ "Value": {
+ "type": "string",
+ "default": "{roomid}-{name}/录制-{roomid}-{date}-{time}-{ms}-{title}.flv"
+ }
+ }
+ },
+ "WpfShowTitleAndArea": {
+ "description": "是否显示直播间标题和分区",
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "HasValue": {
+ "type": "boolean",
+ "default": true
+ },
+ "Value": {
+ "type": "boolean",
+ "default": true
+ }
+ }
+ },
+ "RecordMode": {
+ "description": "录制模式",
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "HasValue": {
+ "type": "boolean",
+ "default": true
+ },
+ "Value": {
+ "type": "integer",
+ "default": 0,
+ "enum": [
+ 0,
+ 1
+ ],
+ "description": "0: Standard\n1: Raw"
+ }
+ }
+ },
+ "CuttingMode": {
+ "description": "录制文件自动切割模式",
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "HasValue": {
+ "type": "boolean",
+ "default": true
+ },
+ "Value": {
+ "type": "integer",
+ "default": 0,
+ "enum": [
+ 0,
+ 1,
+ 2
+ ],
+ "description": "0: 禁用\n1: 根据时间切割\n2: 根据文件大小切割"
+ }
+ }
+ },
+ "CuttingNumber": {
+ "description": "录制文件自动切割数值(分钟/MiB)",
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "HasValue": {
+ "type": "boolean",
+ "default": true
+ },
+ "Value": {
+ "type": "integer",
+ "minimum": 0,
+ "maximum": 4294967295,
+ "default": 100
+ }
+ }
+ },
+ "RecordDanmaku": {
+ "description": "是否同时录制弹幕",
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "HasValue": {
+ "type": "boolean",
+ "default": true
+ },
+ "Value": {
+ "type": "boolean",
+ "default": false
+ }
+ }
+ },
+ "RecordDanmakuRaw": {
+ "description": "是否记录弹幕原始数据",
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "HasValue": {
+ "type": "boolean",
+ "default": true
+ },
+ "Value": {
+ "type": "boolean",
+ "default": false
+ }
+ }
+ },
+ "RecordDanmakuSuperChat": {
+ "description": "是否同时录制 SuperChat",
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "HasValue": {
+ "type": "boolean",
+ "default": true
+ },
+ "Value": {
+ "type": "boolean",
+ "default": true
+ }
+ }
+ },
+ "RecordDanmakuGift": {
+ "description": "是否同时录制 礼物",
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "HasValue": {
+ "type": "boolean",
+ "default": true
+ },
+ "Value": {
+ "type": "boolean",
+ "default": false
+ }
+ }
+ },
+ "RecordDanmakuGuard": {
+ "description": "是否同时录制 上船",
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "HasValue": {
+ "type": "boolean",
+ "default": true
+ },
+ "Value": {
+ "type": "boolean",
+ "default": true
+ }
+ }
+ },
+ "RecordingQuality": {
+ "description": "录制的直播画质 qn 值,逗号分割,靠前的优先",
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "HasValue": {
+ "type": "boolean",
+ "default": true
+ },
+ "Value": {
+ "type": "string",
+ "default": "10000"
+ }
+ }
+ }
+ }
+ },
+ "room-config": {
+ "description": "单个房间配置",
+ "additionalProperties": false,
+ "properties": {
+ "RoomId": {
+ "description": "房间号",
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "HasValue": {
+ "type": "boolean",
+ "default": true
+ },
+ "Value": {
+ "type": "integer",
+ "minimum": -2147483648,
+ "maximum": 2147483647
+ }
+ }
+ },
+ "AutoRecord": {
+ "description": "是否启用自动录制",
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "HasValue": {
+ "type": "boolean",
+ "default": true
+ },
+ "Value": {
+ "type": "boolean"
+ }
+ }
+ },
+ "RecordMode": {
+ "description": "录制模式",
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "HasValue": {
+ "type": "boolean",
+ "default": true
+ },
+ "Value": {
+ "type": "integer",
+ "default": 0,
+ "enum": [
+ 0,
+ 1
+ ],
+ "description": "0: Standard\n1: Raw"
+ }
+ }
+ },
+ "CuttingMode": {
+ "description": "录制文件自动切割模式",
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "HasValue": {
+ "type": "boolean",
+ "default": true
+ },
+ "Value": {
+ "type": "integer",
+ "default": 0,
+ "enum": [
+ 0,
+ 1,
+ 2
+ ],
+ "description": "0: 禁用\n1: 根据时间切割\n2: 根据文件大小切割"
+ }
+ }
+ },
+ "CuttingNumber": {
+ "description": "录制文件自动切割数值(分钟/MiB)",
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "HasValue": {
+ "type": "boolean",
+ "default": true
+ },
+ "Value": {
+ "type": "integer",
+ "minimum": 0,
+ "maximum": 4294967295,
+ "default": 100
+ }
+ }
+ },
+ "RecordDanmaku": {
+ "description": "是否同时录制弹幕",
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "HasValue": {
+ "type": "boolean",
+ "default": true
+ },
+ "Value": {
+ "type": "boolean",
+ "default": false
+ }
+ }
+ },
+ "RecordDanmakuRaw": {
+ "description": "是否记录弹幕原始数据",
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "HasValue": {
+ "type": "boolean",
+ "default": true
+ },
+ "Value": {
+ "type": "boolean",
+ "default": false
+ }
+ }
+ },
+ "RecordDanmakuSuperChat": {
+ "description": "是否同时录制 SuperChat",
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "HasValue": {
+ "type": "boolean",
+ "default": true
+ },
+ "Value": {
+ "type": "boolean",
+ "default": true
+ }
+ }
+ },
+ "RecordDanmakuGift": {
+ "description": "是否同时录制 礼物",
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "HasValue": {
+ "type": "boolean",
+ "default": true
+ },
+ "Value": {
+ "type": "boolean",
+ "default": false
+ }
+ }
+ },
+ "RecordDanmakuGuard": {
+ "description": "是否同时录制 上船",
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "HasValue": {
+ "type": "boolean",
+ "default": true
+ },
+ "Value": {
+ "type": "boolean",
+ "default": true
+ }
+ }
+ },
+ "RecordingQuality": {
+ "description": "录制的直播画质 qn 值,逗号分割,靠前的优先",
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "HasValue": {
+ "type": "boolean",
+ "default": true
+ },
+ "Value": {
+ "type": "string",
+ "default": "10000"
+ }
+ }
+ }
+ }
+ }
+ },
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "$schema",
+ "version"
+ ],
+ "properties": {
+ "$schema": {
+ "type": "string",
+ "default": "https://raw.githubusercontent.com/Bililive/BililiveRecorder/dev-1.3/configV2.schema.json"
+ },
+ "version": {
+ "const": 2
+ },
+ "global": {
+ "$ref": "#/definitions/global-config"
+ },
+ "rooms": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/room-config"
+ }
+ }
+ }
+}
\ No newline at end of file