477 lines
35 KiB
C#
477 lines
35 KiB
C#
using LiveRecorder.Application.Abstractions.Persistence;
|
|
using LiveRecorder.Application.Abstractions.Settings;
|
|
using LiveRecorder.Application.Models.Settings;
|
|
using LiveRecorder.Domain.Entities;
|
|
using LiveRecorder.Domain.Enums;
|
|
|
|
namespace LiveRecorder.Application.Services;
|
|
|
|
public sealed class SystemSettingsService : ISystemSettingsService
|
|
{
|
|
private const string FfmpegPathKey = "ffmpeg.path";
|
|
private const string OutputRootKey = "recording.output_root";
|
|
private const string OutputDirectoryTemplateKey = "recording.output_directory_template";
|
|
private const string OutputFileNameTemplateKey = "recording.output_file_name_template";
|
|
private const string DefaultQualityKey = "recording.default_quality";
|
|
private const string DefaultOutputFormatKey = "recording.default_output_format";
|
|
private const string SaveModeKey = "recording.save_mode";
|
|
private const string RecordingTemplateKey = "recording.template";
|
|
private const string SegmentDurationMinutesKey = "recording.segment_duration_minutes";
|
|
private const string MaxConcurrentFfmpegTranscodeTasksKey = "recording.max_concurrent_ffmpeg_transcode_tasks";
|
|
private const string Mp4FinalizeTimeoutMinutesKey = "recording.mp4_finalize_timeout_minutes";
|
|
private const string EnableStorageGuardKey = "storage.guard.enabled";
|
|
private const string PauseRecordingWhenFreeSpaceBelowMegabytesKey = "storage.guard.pause_recording_below_mb";
|
|
private const string ResumeRecordingWhenFreeSpaceAboveMegabytesKey = "storage.guard.resume_recording_above_mb";
|
|
private const string EnableReconnectKey = "recording.enable_auto_reconnect";
|
|
private const string ReconnectDelayMaxSecondsKey = "recording.reconnect_delay_max_seconds";
|
|
private const string ReadWriteTimeoutMillisecondsKey = "recording.read_write_timeout_milliseconds";
|
|
private const string EnableDanmakuRecordingKey = "recording.enable_danmaku_recording";
|
|
private const string DanmakuIncludeNonChatEventsKey = "recording.danmaku_include_non_chat_events";
|
|
private const string DanmakuMinPollIntervalMillisecondsKey = "recording.danmaku_min_poll_interval_milliseconds";
|
|
private const string DanmakuRetryDelayMaxSecondsKey = "recording.danmaku_retry_delay_max_seconds";
|
|
private const string EnableBackgroundPollingKey = "scheduler.enable_background_polling";
|
|
private const string AutoStartRecordingOnLiveKey = "scheduler.auto_start_recording_on_live";
|
|
private const string PollingIntervalSecondsKey = "scheduler.polling_interval_seconds";
|
|
private const string UseAliasForStorageKey = "recording.use_alias_for_storage";
|
|
private const string EnableFileUploadKey = "upload.enabled";
|
|
private const string EnableAutoUploadKey = "upload.auto_upload";
|
|
private const string DeleteLocalFilesAfterUploadKey = "upload.delete_local_files_after_upload";
|
|
private const string UploadTargetKey = "upload.target";
|
|
private const string WebDavEndpointKey = "upload.webdav.endpoint";
|
|
private const string WebDavBasePathKey = "upload.webdav.base_path";
|
|
private const string WebDavUsernameKey = "upload.webdav.username";
|
|
private const string WebDavPasswordKey = "upload.webdav.password";
|
|
private const string S3EndpointKey = "upload.s3.endpoint";
|
|
private const string S3BucketKey = "upload.s3.bucket";
|
|
private const string S3RegionKey = "upload.s3.region";
|
|
private const string S3AccessKeyKey = "upload.s3.access_key";
|
|
private const string S3SecretKeyKey = "upload.s3.secret_key";
|
|
private const string S3PrefixKey = "upload.s3.prefix";
|
|
private const string S3ForcePathStyleKey = "upload.s3.force_path_style";
|
|
private const string DouyinProxyEnabledKey = "platform_proxy.douyin.enabled";
|
|
private const string DouyinProxyUrlKey = "platform_proxy.douyin.url";
|
|
private const string BilibiliProxyEnabledKey = "platform_proxy.bilibili.enabled";
|
|
private const string BilibiliProxyUrlKey = "platform_proxy.bilibili.url";
|
|
private const string HuyaProxyEnabledKey = "platform_proxy.huya.enabled";
|
|
private const string HuyaProxyUrlKey = "platform_proxy.huya.url";
|
|
private const string EnableEventScriptsKey = "event_scripts.enabled";
|
|
private const string EnableLiveStartedScriptKey = "event_scripts.live_started.enabled";
|
|
private const string LiveStartedScriptModeKey = "event_scripts.live_started.mode";
|
|
private const string LiveStartedScriptPathKey = "event_scripts.live_started.path";
|
|
private const string LiveStartedScriptContentKey = "event_scripts.live_started.content";
|
|
private const string EnableLiveEndedScriptKey = "event_scripts.live_ended.enabled";
|
|
private const string LiveEndedScriptModeKey = "event_scripts.live_ended.mode";
|
|
private const string LiveEndedScriptPathKey = "event_scripts.live_ended.path";
|
|
private const string LiveEndedScriptContentKey = "event_scripts.live_ended.content";
|
|
private const string EnableSegmentCompletedScriptKey = "event_scripts.segment_completed.enabled";
|
|
private const string SegmentCompletedScriptModeKey = "event_scripts.segment_completed.mode";
|
|
private const string SegmentCompletedScriptPathKey = "event_scripts.segment_completed.path";
|
|
private const string SegmentCompletedScriptContentKey = "event_scripts.segment_completed.content";
|
|
private const string EventScriptTimeoutSecondsKey = "event_scripts.timeout_seconds";
|
|
private const string EnableRetentionCleanupKey = "retention.cleanup.enabled";
|
|
private const string RetentionDaysKey = "retention.cleanup.days";
|
|
private const string RetentionDeleteFilesKey = "retention.cleanup.delete_files";
|
|
private const string EnableEmailNotificationKey = "notification.email.enabled";
|
|
private const string EmailSmtpHostKey = "notification.email.smtp_host";
|
|
private const string EmailSmtpPortKey = "notification.email.smtp_port";
|
|
private const string EmailUseSslKey = "notification.email.use_ssl";
|
|
private const string EmailUsernameKey = "notification.email.username";
|
|
private const string EmailPasswordKey = "notification.email.password";
|
|
private const string EmailFromAddressKey = "notification.email.from_address";
|
|
private const string EmailFromDisplayNameKey = "notification.email.from_display_name";
|
|
private const string EmailToAddressesKey = "notification.email.to_addresses";
|
|
private const string NotifyOnLiveStartedKey = "notification.email.notify_live_started";
|
|
private const string NotifyOnExceptionKey = "notification.email.notify_exception";
|
|
private const string EmailLiveStartedSubjectTemplateKey = "notification.email.live_started.subject_template";
|
|
private const string EmailLiveStartedBodyTemplateHtmlKey = "notification.email.live_started.body_template_html";
|
|
private const string EmailExceptionSubjectTemplateKey = "notification.email.exception.subject_template";
|
|
private const string EmailExceptionBodyTemplateHtmlKey = "notification.email.exception.body_template_html";
|
|
private const string EnableWebhookNotificationKey = "notification.webhook.enabled";
|
|
private const string WebhookUrlKey = "notification.webhook.url";
|
|
private const string WebhookHeadersKey = "notification.webhook.headers";
|
|
private const string WebhookTimeoutSecondsKey = "notification.webhook.timeout_seconds";
|
|
private const string NotifyWebhookOnLiveStartedKey = "notification.webhook.notify_live_started";
|
|
private const string NotifyWebhookOnExceptionKey = "notification.webhook.notify_exception";
|
|
private const string DouyinUserAgentKey = "douyin.user_agent";
|
|
private const string DouyinRefererKey = "douyin.referer";
|
|
private const string DouyinCookieKey = "douyin.cookie";
|
|
|
|
private readonly IAppSettingRepository _appSettingRepository;
|
|
private readonly IUnitOfWork _unitOfWork;
|
|
|
|
public SystemSettingsService(IAppSettingRepository appSettingRepository, IUnitOfWork unitOfWork)
|
|
{
|
|
_appSettingRepository = appSettingRepository;
|
|
_unitOfWork = unitOfWork;
|
|
}
|
|
|
|
public async Task<SystemSettingsDto> GetAsync(CancellationToken cancellationToken = default)
|
|
{
|
|
var settings = await _appSettingRepository.ListAsync(cancellationToken);
|
|
var lookup = settings.ToDictionary(static item => item.Key, static item => item.Value, StringComparer.OrdinalIgnoreCase);
|
|
|
|
return new SystemSettingsDto
|
|
{
|
|
FfmpegPath = GetValue(lookup, FfmpegPathKey, "ffmpeg"),
|
|
OutputRoot = GetValue(lookup, OutputRootKey, "records"),
|
|
OutputDirectoryTemplate = GetValue(lookup, OutputDirectoryTemplateKey, "{platform}/{yyyy}/{MM}/{dd}/{anchor}"),
|
|
OutputFileNameTemplate = GetValue(lookup, OutputFileNameTemplateKey, "{HHmmss}_{anchor}_{title}_{roomId}{segmentSuffix}"),
|
|
DefaultQuality = GetValue(lookup, DefaultQualityKey, "origin"),
|
|
DefaultOutputFormat = Enum.TryParse(GetValue(lookup, DefaultOutputFormatKey, "Mp4"), true, out RecordOutputFormat outputFormat)
|
|
? outputFormat
|
|
: RecordOutputFormat.Mp4,
|
|
SaveMode = Enum.TryParse(GetValue(lookup, SaveModeKey, "SingleFile"), true, out RecordSaveMode saveMode)
|
|
? saveMode
|
|
: RecordSaveMode.SingleFile,
|
|
RecordingTemplate = Enum.TryParse(GetValue(lookup, RecordingTemplateKey, "StreamCopy"), true, out RecordingTemplateType recordingTemplate)
|
|
? recordingTemplate
|
|
: RecordingTemplateType.StreamCopy,
|
|
SegmentDurationMinutes = GetIntValue(lookup, SegmentDurationMinutesKey, 30, 1, 720),
|
|
MaxConcurrentFfmpegTranscodeTasks = GetIntValue(lookup, MaxConcurrentFfmpegTranscodeTasksKey, 1, 1, 16),
|
|
Mp4FinalizeTimeoutMinutes = GetIntValue(lookup, Mp4FinalizeTimeoutMinutesKey, 60, 1, 1440),
|
|
EnableStorageGuard = bool.TryParse(GetValue(lookup, EnableStorageGuardKey, "true"), out var enableStorageGuard) && enableStorageGuard,
|
|
PauseRecordingWhenFreeSpaceBelowMegabytes = GetIntValue(lookup, PauseRecordingWhenFreeSpaceBelowMegabytesKey, 1024, 0, 1048576),
|
|
ResumeRecordingWhenFreeSpaceAboveMegabytes = GetIntValue(lookup, ResumeRecordingWhenFreeSpaceAboveMegabytesKey, 4096, 0, 1048576),
|
|
EnableAutoReconnect = bool.TryParse(GetValue(lookup, EnableReconnectKey, "true"), out var enableReconnect) && enableReconnect,
|
|
ReconnectDelayMaxSeconds = GetIntValue(lookup, ReconnectDelayMaxSecondsKey, 5, 1, 300),
|
|
ReadWriteTimeoutMilliseconds = GetIntValue(lookup, ReadWriteTimeoutMillisecondsKey, 15000000, 1000, 60000000),
|
|
EnableDanmakuRecording = bool.TryParse(GetValue(lookup, EnableDanmakuRecordingKey, "true"), out var enableDanmakuRecording) && enableDanmakuRecording,
|
|
DanmakuIncludeNonChatEvents = bool.TryParse(GetValue(lookup, DanmakuIncludeNonChatEventsKey, "true"), out var danmakuIncludeNonChatEvents) && danmakuIncludeNonChatEvents,
|
|
DanmakuMinPollIntervalMilliseconds = GetIntValue(lookup, DanmakuMinPollIntervalMillisecondsKey, 1000, 100, 60000),
|
|
DanmakuRetryDelayMaxSeconds = GetIntValue(lookup, DanmakuRetryDelayMaxSecondsKey, 15, 1, 300),
|
|
EnableBackgroundPolling = bool.TryParse(GetValue(lookup, EnableBackgroundPollingKey, "true"), out var enableBackgroundPolling) && enableBackgroundPolling,
|
|
AutoStartRecordingOnLive = bool.TryParse(GetValue(lookup, AutoStartRecordingOnLiveKey, "true"), out var autoStartRecordingOnLive) && autoStartRecordingOnLive,
|
|
PollingIntervalSeconds = GetIntValue(lookup, PollingIntervalSecondsKey, 60, 10, 3600),
|
|
UseAliasForStorage = bool.TryParse(GetValue(lookup, UseAliasForStorageKey, "false"), out var useAliasForStorage) && useAliasForStorage,
|
|
EnableFileUpload = bool.TryParse(GetValue(lookup, EnableFileUploadKey, "false"), out var enableFileUpload) && enableFileUpload,
|
|
EnableAutoUpload = bool.TryParse(GetValue(lookup, EnableAutoUploadKey, "false"), out var enableAutoUpload) && enableAutoUpload,
|
|
DeleteLocalFilesAfterUpload = bool.TryParse(GetValue(lookup, DeleteLocalFilesAfterUploadKey, "false"), out var deleteLocalFilesAfterUpload) && deleteLocalFilesAfterUpload,
|
|
UploadTarget = Enum.TryParse(GetValue(lookup, UploadTargetKey, "None"), true, out UploadTargetType uploadTarget)
|
|
? uploadTarget
|
|
: UploadTargetType.None,
|
|
DouyinProxy = new PlatformProxySettingsDto
|
|
{
|
|
Enabled = bool.TryParse(GetValue(lookup, DouyinProxyEnabledKey, "false"), out var douyinProxyEnabled) && douyinProxyEnabled,
|
|
ProxyUrl = GetValue(lookup, DouyinProxyUrlKey, string.Empty)
|
|
},
|
|
BilibiliProxy = new PlatformProxySettingsDto
|
|
{
|
|
Enabled = bool.TryParse(GetValue(lookup, BilibiliProxyEnabledKey, "false"), out var bilibiliProxyEnabled) && bilibiliProxyEnabled,
|
|
ProxyUrl = GetValue(lookup, BilibiliProxyUrlKey, string.Empty)
|
|
},
|
|
HuyaProxy = new PlatformProxySettingsDto
|
|
{
|
|
Enabled = bool.TryParse(GetValue(lookup, HuyaProxyEnabledKey, "false"), out var huyaProxyEnabled) && huyaProxyEnabled,
|
|
ProxyUrl = GetValue(lookup, HuyaProxyUrlKey, string.Empty)
|
|
},
|
|
WebDavUpload = new WebDavUploadSettingsDto
|
|
{
|
|
Endpoint = GetValue(lookup, WebDavEndpointKey, string.Empty),
|
|
BasePath = GetValue(lookup, WebDavBasePathKey, string.Empty),
|
|
Username = GetValue(lookup, WebDavUsernameKey, string.Empty),
|
|
Password = GetValue(lookup, WebDavPasswordKey, string.Empty)
|
|
},
|
|
S3Upload = new S3UploadSettingsDto
|
|
{
|
|
Endpoint = GetValue(lookup, S3EndpointKey, string.Empty),
|
|
Bucket = GetValue(lookup, S3BucketKey, string.Empty),
|
|
Region = GetValue(lookup, S3RegionKey, string.Empty),
|
|
AccessKey = GetValue(lookup, S3AccessKeyKey, string.Empty),
|
|
SecretKey = GetValue(lookup, S3SecretKeyKey, string.Empty),
|
|
Prefix = GetValue(lookup, S3PrefixKey, string.Empty),
|
|
ForcePathStyle = bool.TryParse(GetValue(lookup, S3ForcePathStyleKey, "false"), out var s3ForcePathStyle) && s3ForcePathStyle
|
|
},
|
|
EnableEventScripts = bool.TryParse(GetValue(lookup, EnableEventScriptsKey, "false"), out var enableEventScripts) && enableEventScripts,
|
|
EnableLiveStartedScript = GetEventScriptEnabled(
|
|
lookup,
|
|
EnableLiveStartedScriptKey,
|
|
enableEventScripts,
|
|
LiveStartedScriptPathKey,
|
|
LiveStartedScriptContentKey),
|
|
LiveStartedScriptMode = GetEventScriptMode(lookup, LiveStartedScriptModeKey, LiveStartedScriptPathKey, LiveStartedScriptContentKey),
|
|
LiveStartedScriptPath = GetValue(lookup, LiveStartedScriptPathKey, string.Empty),
|
|
LiveStartedScriptContent = GetValue(lookup, LiveStartedScriptContentKey, string.Empty),
|
|
EnableLiveEndedScript = GetEventScriptEnabled(
|
|
lookup,
|
|
EnableLiveEndedScriptKey,
|
|
enableEventScripts,
|
|
LiveEndedScriptPathKey,
|
|
LiveEndedScriptContentKey),
|
|
LiveEndedScriptMode = GetEventScriptMode(lookup, LiveEndedScriptModeKey, LiveEndedScriptPathKey, LiveEndedScriptContentKey),
|
|
LiveEndedScriptPath = GetValue(lookup, LiveEndedScriptPathKey, string.Empty),
|
|
LiveEndedScriptContent = GetValue(lookup, LiveEndedScriptContentKey, string.Empty),
|
|
EnableSegmentCompletedScript = GetEventScriptEnabled(
|
|
lookup,
|
|
EnableSegmentCompletedScriptKey,
|
|
enableEventScripts,
|
|
SegmentCompletedScriptPathKey,
|
|
SegmentCompletedScriptContentKey),
|
|
SegmentCompletedScriptMode = GetEventScriptMode(lookup, SegmentCompletedScriptModeKey, SegmentCompletedScriptPathKey, SegmentCompletedScriptContentKey),
|
|
SegmentCompletedScriptPath = GetValue(lookup, SegmentCompletedScriptPathKey, string.Empty),
|
|
SegmentCompletedScriptContent = GetValue(lookup, SegmentCompletedScriptContentKey, string.Empty),
|
|
EventScriptTimeoutSeconds = GetIntValue(lookup, EventScriptTimeoutSecondsKey, 60, 1, 3600),
|
|
EnableRetentionCleanup = bool.TryParse(GetValue(lookup, EnableRetentionCleanupKey, "false"), out var enableRetentionCleanup) && enableRetentionCleanup,
|
|
RetentionDays = GetIntValue(lookup, RetentionDaysKey, 30, 1, 3650),
|
|
RetentionDeleteFiles = bool.TryParse(GetValue(lookup, RetentionDeleteFilesKey, "false"), out var retentionDeleteFiles) && retentionDeleteFiles,
|
|
EnableEmailNotification = bool.TryParse(GetValue(lookup, EnableEmailNotificationKey, "false"), out var enableEmailNotification) && enableEmailNotification,
|
|
EmailSmtpHost = GetValue(lookup, EmailSmtpHostKey, string.Empty),
|
|
EmailSmtpPort = GetIntValue(lookup, EmailSmtpPortKey, 587, 1, 65535),
|
|
EmailUseSsl = bool.TryParse(GetValue(lookup, EmailUseSslKey, "true"), out var emailUseSsl) && emailUseSsl,
|
|
EmailUsername = GetValue(lookup, EmailUsernameKey, string.Empty),
|
|
EmailPassword = GetValue(lookup, EmailPasswordKey, string.Empty),
|
|
EmailFromAddress = GetValue(lookup, EmailFromAddressKey, string.Empty),
|
|
EmailFromDisplayName = GetValue(lookup, EmailFromDisplayNameKey, "Live Recorder"),
|
|
EmailToAddresses = GetValue(lookup, EmailToAddressesKey, string.Empty),
|
|
NotifyOnLiveStarted = bool.TryParse(GetValue(lookup, NotifyOnLiveStartedKey, "true"), out var notifyOnLiveStarted) && notifyOnLiveStarted,
|
|
NotifyOnException = bool.TryParse(GetValue(lookup, NotifyOnExceptionKey, "true"), out var notifyOnException) && notifyOnException,
|
|
EmailLiveStartedSubjectTemplate = GetValue(lookup, EmailLiveStartedSubjectTemplateKey, "[{{appName}}] Live started: {{anchor}} {{title}} ({{roomId}})"),
|
|
EmailLiveStartedBodyTemplateHtml = GetValue(
|
|
lookup,
|
|
EmailLiveStartedBodyTemplateHtmlKey,
|
|
"""
|
|
<div style="font-family: 'Segoe UI', 'PingFang SC', sans-serif; color: #1f2937; line-height: 1.7;">
|
|
<h2 style="margin: 0 0 16px; color: #3e5f7c;">Live started</h2>
|
|
<p>The monitored live room is now online.</p>
|
|
<ul>
|
|
<li><strong>Platform:</strong> {{platform}}</li>
|
|
<li><strong>Room ID:</strong> {{roomId}}</li>
|
|
<li><strong>Title:</strong> {{title}}</li>
|
|
<li><strong>Anchor:</strong> {{anchor}}</li>
|
|
<li><strong>Detected At (UTC):</strong> {{detectedAtUtc}}</li>
|
|
</ul>
|
|
<p><strong>Source URL:</strong> <a href="{{sourceUrl}}">{{sourceUrl}}</a></p>
|
|
</div>
|
|
"""),
|
|
EmailExceptionSubjectTemplate = GetValue(lookup, EmailExceptionSubjectTemplateKey, "[{{appName}}] Exception: {{source}}"),
|
|
EmailExceptionBodyTemplateHtml = GetValue(
|
|
lookup,
|
|
EmailExceptionBodyTemplateHtmlKey,
|
|
"""
|
|
<div style="font-family: 'Segoe UI', 'PingFang SC', sans-serif; color: #1f2937; line-height: 1.7;">
|
|
<h2 style="margin: 0 0 16px; color: #8b5e3c;">Exception detected</h2>
|
|
<p>{{summary}}</p>
|
|
<ul>
|
|
<li><strong>Source:</strong> {{source}}</li>
|
|
<li><strong>Live Room ID:</strong> {{liveRoomId}}</li>
|
|
<li><strong>Room ID:</strong> {{roomId}}</li>
|
|
<li><strong>Record Task ID:</strong> {{recordTaskId}}</li>
|
|
<li><strong>Task Status:</strong> {{taskStatus}}</li>
|
|
<li><strong>Occurred At (UTC):</strong> {{occurredAtUtc}}</li>
|
|
</ul>
|
|
<div style="margin-top: 16px; padding: 12px 14px; border-radius: 8px; background: #f5f5f5; white-space: pre-wrap;">{{detail}}</div>
|
|
</div>
|
|
"""),
|
|
EnableWebhookNotification = bool.TryParse(GetValue(lookup, EnableWebhookNotificationKey, "false"), out var enableWebhookNotification) && enableWebhookNotification,
|
|
WebhookUrl = GetValue(lookup, WebhookUrlKey, string.Empty),
|
|
WebhookHeaders = GetValue(lookup, WebhookHeadersKey, string.Empty),
|
|
WebhookTimeoutSeconds = GetIntValue(lookup, WebhookTimeoutSecondsKey, 15, 1, 300),
|
|
NotifyWebhookOnLiveStarted = bool.TryParse(GetValue(lookup, NotifyWebhookOnLiveStartedKey, "true"), out var notifyWebhookOnLiveStarted) && notifyWebhookOnLiveStarted,
|
|
NotifyWebhookOnException = bool.TryParse(GetValue(lookup, NotifyWebhookOnExceptionKey, "true"), out var notifyWebhookOnException) && notifyWebhookOnException,
|
|
DouyinUserAgent = GetValue(
|
|
lookup,
|
|
DouyinUserAgentKey,
|
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36"),
|
|
DouyinReferer = GetValue(lookup, DouyinRefererKey, "https://live.douyin.com/"),
|
|
DouyinCookie = GetValue(lookup, DouyinCookieKey, string.Empty)
|
|
};
|
|
}
|
|
|
|
public async Task<SystemSettingsDto> UpdateAsync(UpdateSystemSettingsRequest request, CancellationToken cancellationToken = default)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(request);
|
|
|
|
var now = DateTimeOffset.UtcNow;
|
|
var douyinProxy = request.DouyinProxy ?? new PlatformProxySettingsDto();
|
|
var bilibiliProxy = request.BilibiliProxy ?? new PlatformProxySettingsDto();
|
|
var huyaProxy = request.HuyaProxy ?? new PlatformProxySettingsDto();
|
|
var webDavUpload = request.WebDavUpload ?? new WebDavUploadSettingsDto();
|
|
var s3Upload = request.S3Upload ?? new S3UploadSettingsDto();
|
|
|
|
await UpsertAsync(FfmpegPathKey, request.FfmpegPath.Trim(), now, cancellationToken);
|
|
await UpsertAsync(OutputRootKey, request.OutputRoot.Trim(), now, cancellationToken);
|
|
await UpsertAsync(OutputDirectoryTemplateKey, request.OutputDirectoryTemplate.Trim(), now, cancellationToken);
|
|
await UpsertAsync(OutputFileNameTemplateKey, request.OutputFileNameTemplate.Trim(), now, cancellationToken);
|
|
await UpsertAsync(DefaultQualityKey, request.DefaultQuality.Trim(), now, cancellationToken);
|
|
await UpsertAsync(DefaultOutputFormatKey, request.DefaultOutputFormat.ToString(), now, cancellationToken);
|
|
await UpsertAsync(SaveModeKey, request.SaveMode.ToString(), now, cancellationToken);
|
|
await UpsertAsync(RecordingTemplateKey, request.RecordingTemplate.ToString(), now, cancellationToken);
|
|
await UpsertAsync(SegmentDurationMinutesKey, request.SegmentDurationMinutes.ToString(), now, cancellationToken);
|
|
await UpsertAsync(
|
|
MaxConcurrentFfmpegTranscodeTasksKey,
|
|
Math.Clamp(request.MaxConcurrentFfmpegTranscodeTasks, 1, 16).ToString(),
|
|
now,
|
|
cancellationToken);
|
|
await UpsertAsync(
|
|
Mp4FinalizeTimeoutMinutesKey,
|
|
Math.Clamp(request.Mp4FinalizeTimeoutMinutes, 1, 1440).ToString(),
|
|
now,
|
|
cancellationToken);
|
|
await UpsertAsync(EnableStorageGuardKey, request.EnableStorageGuard.ToString(), now, cancellationToken);
|
|
await UpsertAsync(
|
|
PauseRecordingWhenFreeSpaceBelowMegabytesKey,
|
|
Math.Clamp(request.PauseRecordingWhenFreeSpaceBelowMegabytes, 0, 1048576).ToString(),
|
|
now,
|
|
cancellationToken);
|
|
await UpsertAsync(
|
|
ResumeRecordingWhenFreeSpaceAboveMegabytesKey,
|
|
Math.Clamp(request.ResumeRecordingWhenFreeSpaceAboveMegabytes, 0, 1048576).ToString(),
|
|
now,
|
|
cancellationToken);
|
|
await UpsertAsync(EnableReconnectKey, request.EnableAutoReconnect.ToString(), now, cancellationToken);
|
|
await UpsertAsync(ReconnectDelayMaxSecondsKey, request.ReconnectDelayMaxSeconds.ToString(), now, cancellationToken);
|
|
await UpsertAsync(ReadWriteTimeoutMillisecondsKey, request.ReadWriteTimeoutMilliseconds.ToString(), now, cancellationToken);
|
|
await UpsertAsync(EnableDanmakuRecordingKey, request.EnableDanmakuRecording.ToString(), now, cancellationToken);
|
|
await UpsertAsync(DanmakuIncludeNonChatEventsKey, request.DanmakuIncludeNonChatEvents.ToString(), now, cancellationToken);
|
|
await UpsertAsync(DanmakuMinPollIntervalMillisecondsKey, request.DanmakuMinPollIntervalMilliseconds.ToString(), now, cancellationToken);
|
|
await UpsertAsync(DanmakuRetryDelayMaxSecondsKey, request.DanmakuRetryDelayMaxSeconds.ToString(), now, cancellationToken);
|
|
await UpsertAsync(EnableBackgroundPollingKey, request.EnableBackgroundPolling.ToString(), now, cancellationToken);
|
|
await UpsertAsync(AutoStartRecordingOnLiveKey, request.AutoStartRecordingOnLive.ToString(), now, cancellationToken);
|
|
await UpsertAsync(PollingIntervalSecondsKey, request.PollingIntervalSeconds.ToString(), now, cancellationToken);
|
|
await UpsertAsync(UseAliasForStorageKey, request.UseAliasForStorage.ToString(), now, cancellationToken);
|
|
await UpsertAsync(EnableFileUploadKey, request.EnableFileUpload.ToString(), now, cancellationToken);
|
|
await UpsertAsync(EnableAutoUploadKey, request.EnableAutoUpload.ToString(), now, cancellationToken);
|
|
await UpsertAsync(DeleteLocalFilesAfterUploadKey, request.DeleteLocalFilesAfterUpload.ToString(), now, cancellationToken);
|
|
await UpsertAsync(UploadTargetKey, request.UploadTarget.ToString(), now, cancellationToken);
|
|
await UpsertAsync(WebDavEndpointKey, webDavUpload.Endpoint.Trim(), now, cancellationToken);
|
|
await UpsertAsync(WebDavBasePathKey, webDavUpload.BasePath.Trim(), now, cancellationToken);
|
|
await UpsertAsync(WebDavUsernameKey, webDavUpload.Username.Trim(), now, cancellationToken);
|
|
await UpsertAsync(WebDavPasswordKey, webDavUpload.Password, now, cancellationToken);
|
|
await UpsertAsync(S3EndpointKey, s3Upload.Endpoint.Trim(), now, cancellationToken);
|
|
await UpsertAsync(S3BucketKey, s3Upload.Bucket.Trim(), now, cancellationToken);
|
|
await UpsertAsync(S3RegionKey, s3Upload.Region.Trim(), now, cancellationToken);
|
|
await UpsertAsync(S3AccessKeyKey, s3Upload.AccessKey.Trim(), now, cancellationToken);
|
|
await UpsertAsync(S3SecretKeyKey, s3Upload.SecretKey, now, cancellationToken);
|
|
await UpsertAsync(S3PrefixKey, s3Upload.Prefix.Trim(), now, cancellationToken);
|
|
await UpsertAsync(S3ForcePathStyleKey, s3Upload.ForcePathStyle.ToString(), now, cancellationToken);
|
|
await UpsertAsync(DouyinProxyEnabledKey, douyinProxy.Enabled.ToString(), now, cancellationToken);
|
|
await UpsertAsync(DouyinProxyUrlKey, douyinProxy.ProxyUrl.Trim(), now, cancellationToken);
|
|
await UpsertAsync(BilibiliProxyEnabledKey, bilibiliProxy.Enabled.ToString(), now, cancellationToken);
|
|
await UpsertAsync(BilibiliProxyUrlKey, bilibiliProxy.ProxyUrl.Trim(), now, cancellationToken);
|
|
await UpsertAsync(HuyaProxyEnabledKey, huyaProxy.Enabled.ToString(), now, cancellationToken);
|
|
await UpsertAsync(HuyaProxyUrlKey, huyaProxy.ProxyUrl.Trim(), now, cancellationToken);
|
|
await UpsertAsync(EnableEventScriptsKey, request.EnableEventScripts.ToString(), now, cancellationToken);
|
|
await UpsertAsync(EnableLiveStartedScriptKey, request.EnableLiveStartedScript.ToString(), now, cancellationToken);
|
|
await UpsertAsync(LiveStartedScriptModeKey, NormalizeEventScriptMode(request.LiveStartedScriptMode), now, cancellationToken);
|
|
await UpsertAsync(LiveStartedScriptPathKey, request.LiveStartedScriptPath.Trim(), now, cancellationToken);
|
|
await UpsertAsync(LiveStartedScriptContentKey, request.LiveStartedScriptContent, now, cancellationToken);
|
|
await UpsertAsync(EnableLiveEndedScriptKey, request.EnableLiveEndedScript.ToString(), now, cancellationToken);
|
|
await UpsertAsync(LiveEndedScriptModeKey, NormalizeEventScriptMode(request.LiveEndedScriptMode), now, cancellationToken);
|
|
await UpsertAsync(LiveEndedScriptPathKey, request.LiveEndedScriptPath.Trim(), now, cancellationToken);
|
|
await UpsertAsync(LiveEndedScriptContentKey, request.LiveEndedScriptContent, now, cancellationToken);
|
|
await UpsertAsync(EnableSegmentCompletedScriptKey, request.EnableSegmentCompletedScript.ToString(), now, cancellationToken);
|
|
await UpsertAsync(SegmentCompletedScriptModeKey, NormalizeEventScriptMode(request.SegmentCompletedScriptMode), now, cancellationToken);
|
|
await UpsertAsync(SegmentCompletedScriptPathKey, request.SegmentCompletedScriptPath.Trim(), now, cancellationToken);
|
|
await UpsertAsync(SegmentCompletedScriptContentKey, request.SegmentCompletedScriptContent, now, cancellationToken);
|
|
await UpsertAsync(EventScriptTimeoutSecondsKey, Math.Clamp(request.EventScriptTimeoutSeconds, 1, 3600).ToString(), now, cancellationToken);
|
|
await UpsertAsync(EnableRetentionCleanupKey, request.EnableRetentionCleanup.ToString(), now, cancellationToken);
|
|
await UpsertAsync(RetentionDaysKey, Math.Clamp(request.RetentionDays, 1, 3650).ToString(), now, cancellationToken);
|
|
await UpsertAsync(RetentionDeleteFilesKey, request.RetentionDeleteFiles.ToString(), now, cancellationToken);
|
|
await UpsertAsync(EnableEmailNotificationKey, request.EnableEmailNotification.ToString(), now, cancellationToken);
|
|
await UpsertAsync(EmailSmtpHostKey, request.EmailSmtpHost.Trim(), now, cancellationToken);
|
|
await UpsertAsync(EmailSmtpPortKey, request.EmailSmtpPort.ToString(), now, cancellationToken);
|
|
await UpsertAsync(EmailUseSslKey, request.EmailUseSsl.ToString(), now, cancellationToken);
|
|
await UpsertAsync(EmailUsernameKey, request.EmailUsername.Trim(), now, cancellationToken);
|
|
await UpsertAsync(EmailPasswordKey, request.EmailPassword, now, cancellationToken);
|
|
await UpsertAsync(EmailFromAddressKey, request.EmailFromAddress.Trim(), now, cancellationToken);
|
|
await UpsertAsync(EmailFromDisplayNameKey, request.EmailFromDisplayName.Trim(), now, cancellationToken);
|
|
await UpsertAsync(EmailToAddressesKey, request.EmailToAddresses.Trim(), now, cancellationToken);
|
|
await UpsertAsync(NotifyOnLiveStartedKey, request.NotifyOnLiveStarted.ToString(), now, cancellationToken);
|
|
await UpsertAsync(NotifyOnExceptionKey, request.NotifyOnException.ToString(), now, cancellationToken);
|
|
await UpsertAsync(EmailLiveStartedSubjectTemplateKey, request.EmailLiveStartedSubjectTemplate.Trim(), now, cancellationToken);
|
|
await UpsertAsync(EmailLiveStartedBodyTemplateHtmlKey, request.EmailLiveStartedBodyTemplateHtml.Trim(), now, cancellationToken);
|
|
await UpsertAsync(EmailExceptionSubjectTemplateKey, request.EmailExceptionSubjectTemplate.Trim(), now, cancellationToken);
|
|
await UpsertAsync(EmailExceptionBodyTemplateHtmlKey, request.EmailExceptionBodyTemplateHtml.Trim(), now, cancellationToken);
|
|
await UpsertAsync(EnableWebhookNotificationKey, request.EnableWebhookNotification.ToString(), now, cancellationToken);
|
|
await UpsertAsync(WebhookUrlKey, request.WebhookUrl.Trim(), now, cancellationToken);
|
|
await UpsertAsync(WebhookHeadersKey, request.WebhookHeaders, now, cancellationToken);
|
|
await UpsertAsync(WebhookTimeoutSecondsKey, Math.Clamp(request.WebhookTimeoutSeconds, 1, 300).ToString(), now, cancellationToken);
|
|
await UpsertAsync(NotifyWebhookOnLiveStartedKey, request.NotifyWebhookOnLiveStarted.ToString(), now, cancellationToken);
|
|
await UpsertAsync(NotifyWebhookOnExceptionKey, request.NotifyWebhookOnException.ToString(), now, cancellationToken);
|
|
await UpsertAsync(DouyinUserAgentKey, request.DouyinUserAgent.Trim(), now, cancellationToken);
|
|
await UpsertAsync(DouyinRefererKey, request.DouyinReferer.Trim(), now, cancellationToken);
|
|
await UpsertAsync(DouyinCookieKey, request.DouyinCookie.Trim(), now, cancellationToken);
|
|
|
|
await _unitOfWork.SaveChangesAsync(cancellationToken);
|
|
return await GetAsync(cancellationToken);
|
|
}
|
|
|
|
private static string GetValue(IReadOnlyDictionary<string, string> lookup, string key, string fallback) =>
|
|
lookup.TryGetValue(key, out var value) && !string.IsNullOrWhiteSpace(value) ? value : fallback;
|
|
|
|
private static string GetEventScriptMode(
|
|
IReadOnlyDictionary<string, string> lookup,
|
|
string modeKey,
|
|
string pathKey,
|
|
string contentKey)
|
|
{
|
|
if (lookup.TryGetValue(modeKey, out var configuredMode) && !string.IsNullOrWhiteSpace(configuredMode))
|
|
{
|
|
return NormalizeEventScriptMode(configuredMode);
|
|
}
|
|
|
|
var configuredPath = GetValue(lookup, pathKey, string.Empty);
|
|
var configuredContent = GetValue(lookup, contentKey, string.Empty);
|
|
return string.IsNullOrWhiteSpace(configuredContent) || !string.IsNullOrWhiteSpace(configuredPath)
|
|
? EventScriptSourceModes.Path
|
|
: EventScriptSourceModes.Inline;
|
|
}
|
|
|
|
private static int GetIntValue(
|
|
IReadOnlyDictionary<string, string> lookup,
|
|
string key,
|
|
int fallback,
|
|
int minimum,
|
|
int maximum)
|
|
{
|
|
var raw = GetValue(lookup, key, fallback.ToString());
|
|
if (!int.TryParse(raw, out var parsedValue))
|
|
{
|
|
return fallback;
|
|
}
|
|
|
|
return Math.Clamp(parsedValue, minimum, maximum);
|
|
}
|
|
|
|
private static string NormalizeEventScriptMode(string? value) =>
|
|
string.Equals(value?.Trim(), EventScriptSourceModes.Inline, StringComparison.OrdinalIgnoreCase)
|
|
? EventScriptSourceModes.Inline
|
|
: EventScriptSourceModes.Path;
|
|
|
|
private async Task UpsertAsync(string key, string value, DateTimeOffset updatedAt, CancellationToken cancellationToken)
|
|
{
|
|
var existing = await _appSettingRepository.GetByKeyAsync(key, cancellationToken);
|
|
if (existing is null)
|
|
{
|
|
await _appSettingRepository.AddAsync(new AppSetting(key, value, updatedAt), cancellationToken);
|
|
return;
|
|
}
|
|
|
|
existing.Update(value, updatedAt);
|
|
_appSettingRepository.Update(existing);
|
|
}
|
|
|
|
private static bool GetEventScriptEnabled(
|
|
IReadOnlyDictionary<string, string> lookup,
|
|
string enabledKey,
|
|
bool legacyGlobalEnabled,
|
|
string pathKey,
|
|
string contentKey)
|
|
{
|
|
if (lookup.TryGetValue(enabledKey, out var configured) && bool.TryParse(configured, out var enabled))
|
|
{
|
|
return enabled;
|
|
}
|
|
|
|
return legacyGlobalEnabled && HasAnyConfiguredValue(lookup, pathKey, contentKey);
|
|
}
|
|
|
|
private static bool HasAnyConfiguredValue(
|
|
IReadOnlyDictionary<string, string> lookup,
|
|
string pathKey,
|
|
string contentKey)
|
|
{
|
|
return !string.IsNullOrWhiteSpace(GetValue(lookup, pathKey, string.Empty))
|
|
|| !string.IsNullOrWhiteSpace(GetValue(lookup, contentKey, string.Empty));
|
|
}
|
|
}
|