feat: support inline event scripts
This commit is contained in:
@@ -216,9 +216,15 @@ export interface SystemSettings {
|
||||
autoStartRecordingOnLive: boolean;
|
||||
pollingIntervalSeconds: number;
|
||||
enableEventScripts: boolean;
|
||||
liveStartedScriptMode: string;
|
||||
liveStartedScriptPath: string;
|
||||
liveStartedScriptContent: string;
|
||||
liveEndedScriptMode: string;
|
||||
liveEndedScriptPath: string;
|
||||
liveEndedScriptContent: string;
|
||||
segmentCompletedScriptMode: string;
|
||||
segmentCompletedScriptPath: string;
|
||||
segmentCompletedScriptContent: string;
|
||||
eventScriptTimeoutSeconds: number;
|
||||
enableEmailNotification: boolean;
|
||||
emailSmtpHost: string;
|
||||
|
||||
@@ -36,9 +36,15 @@ const form = reactive<SystemSettings>({
|
||||
autoStartRecordingOnLive: true,
|
||||
pollingIntervalSeconds: 60,
|
||||
enableEventScripts: false,
|
||||
liveStartedScriptMode: "path",
|
||||
liveStartedScriptPath: "",
|
||||
liveStartedScriptContent: "",
|
||||
liveEndedScriptMode: "path",
|
||||
liveEndedScriptPath: "",
|
||||
liveEndedScriptContent: "",
|
||||
segmentCompletedScriptMode: "path",
|
||||
segmentCompletedScriptPath: "",
|
||||
segmentCompletedScriptContent: "",
|
||||
eventScriptTimeoutSeconds: 60,
|
||||
enableEmailNotification: false,
|
||||
emailSmtpHost: "",
|
||||
@@ -135,6 +141,11 @@ const eventScriptEnvironmentExamples = [
|
||||
{ name: "LIVE_RECORDER_SESSION_STATUS", example: "Running", scope: "仅分片完成" }
|
||||
];
|
||||
|
||||
const eventScriptModeOptions = [
|
||||
{ label: "路径", value: "path" },
|
||||
{ label: "脚本文本", value: "inline" }
|
||||
];
|
||||
|
||||
const canSendTestEmail = computed(() =>
|
||||
Boolean(form.emailSmtpHost.trim() && form.emailFromAddress.trim() && form.emailToAddresses.trim())
|
||||
);
|
||||
@@ -482,19 +493,91 @@ onMounted(loadSettings);
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="开播脚本路径">
|
||||
<el-input v-model="form.liveStartedScriptPath" :disabled="!form.enableEventScripts" placeholder="/app/scripts/live-started.sh" />
|
||||
</el-form-item>
|
||||
<div class="event-script-section">
|
||||
<div class="event-script-section__header">
|
||||
<h4 class="event-script-section__title">开播</h4>
|
||||
<el-radio-group v-model="form.liveStartedScriptMode" size="small" :disabled="!form.enableEventScripts">
|
||||
<el-radio-button
|
||||
v-for="option in eventScriptModeOptions"
|
||||
:key="`live-started-${option.value}`"
|
||||
:label="option.value">
|
||||
{{ option.label }}
|
||||
</el-radio-button>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
<el-form-item v-if="form.liveStartedScriptMode === 'path'" label="开播脚本路径">
|
||||
<el-input
|
||||
v-model="form.liveStartedScriptPath"
|
||||
:disabled="!form.enableEventScripts"
|
||||
placeholder="/app/scripts/live-started.sh" />
|
||||
</el-form-item>
|
||||
<el-form-item v-else label="开播脚本文本">
|
||||
<el-input
|
||||
v-model="form.liveStartedScriptContent"
|
||||
:disabled="!form.enableEventScripts"
|
||||
type="textarea"
|
||||
:rows="6"
|
||||
placeholder='echo "started: $LIVE_RECORDER_ROOM_ID"' />
|
||||
</el-form-item>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="下播脚本路径">
|
||||
<el-input v-model="form.liveEndedScriptPath" :disabled="!form.enableEventScripts" placeholder="/app/scripts/live-ended.sh" />
|
||||
</el-form-item>
|
||||
<div class="event-script-section">
|
||||
<div class="event-script-section__header">
|
||||
<h4 class="event-script-section__title">下播</h4>
|
||||
<el-radio-group v-model="form.liveEndedScriptMode" size="small" :disabled="!form.enableEventScripts">
|
||||
<el-radio-button
|
||||
v-for="option in eventScriptModeOptions"
|
||||
:key="`live-ended-${option.value}`"
|
||||
:label="option.value">
|
||||
{{ option.label }}
|
||||
</el-radio-button>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
<el-form-item v-if="form.liveEndedScriptMode === 'path'" label="下播脚本路径">
|
||||
<el-input
|
||||
v-model="form.liveEndedScriptPath"
|
||||
:disabled="!form.enableEventScripts"
|
||||
placeholder="/app/scripts/live-ended.sh" />
|
||||
</el-form-item>
|
||||
<el-form-item v-else label="下播脚本文本">
|
||||
<el-input
|
||||
v-model="form.liveEndedScriptContent"
|
||||
:disabled="!form.enableEventScripts"
|
||||
type="textarea"
|
||||
:rows="6"
|
||||
placeholder='echo "ended: $LIVE_RECORDER_ROOM_ID"' />
|
||||
</el-form-item>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="分片完成脚本路径">
|
||||
<el-input v-model="form.segmentCompletedScriptPath" :disabled="!form.enableEventScripts" placeholder="/app/scripts/segment-completed.sh" />
|
||||
</el-form-item>
|
||||
<div class="event-script-section">
|
||||
<div class="event-script-section__header">
|
||||
<h4 class="event-script-section__title">分片完成</h4>
|
||||
<el-radio-group v-model="form.segmentCompletedScriptMode" size="small" :disabled="!form.enableEventScripts">
|
||||
<el-radio-button
|
||||
v-for="option in eventScriptModeOptions"
|
||||
:key="`segment-completed-${option.value}`"
|
||||
:label="option.value">
|
||||
{{ option.label }}
|
||||
</el-radio-button>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
<el-form-item v-if="form.segmentCompletedScriptMode === 'path'" label="分片完成脚本路径">
|
||||
<el-input
|
||||
v-model="form.segmentCompletedScriptPath"
|
||||
:disabled="!form.enableEventScripts"
|
||||
placeholder="/app/scripts/segment-completed.sh" />
|
||||
</el-form-item>
|
||||
<el-form-item v-else label="分片完成脚本文本">
|
||||
<el-input
|
||||
v-model="form.segmentCompletedScriptContent"
|
||||
:disabled="!form.enableEventScripts"
|
||||
type="textarea"
|
||||
:rows="8"
|
||||
placeholder='echo "$LIVE_RECORDER_SEGMENT_FILE_PATH"' />
|
||||
</el-form-item>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
@@ -504,6 +587,10 @@ onMounted(loadSettings);
|
||||
开播/下播脚本会带“全部事件”里的变量;分片完成脚本会额外带分片路径、弹幕路径、时长、文件大小、任务状态这些值。取不到时会传空字符串。
|
||||
</div>
|
||||
|
||||
<div class="event-script-help__intro">
|
||||
脚本文本模式在 Linux 或 Docker 中通过 <code>/bin/sh -c</code> 执行,在 Windows 中通过 <code>PowerShell -Command</code> 执行。
|
||||
</div>
|
||||
|
||||
<div class="event-script-example">
|
||||
<div class="event-script-example__label">环境变量示例值</div>
|
||||
<div class="event-script-example__grid">
|
||||
@@ -734,6 +821,28 @@ onMounted(loadSettings);
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.event-script-section {
|
||||
padding: 16px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid rgba(91, 110, 129, 0.12);
|
||||
background: rgba(255, 255, 255, 0.72);
|
||||
}
|
||||
|
||||
.event-script-section__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.event-script-section__title {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
color: #1e2937;
|
||||
}
|
||||
|
||||
.event-script-example {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
@@ -860,6 +969,11 @@ onMounted(loadSettings);
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.event-script-section__header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.event-script-example__row {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 4px;
|
||||
|
||||
@@ -2,6 +2,13 @@ using LiveRecorder.Domain.Enums;
|
||||
|
||||
namespace LiveRecorder.Application.Models.Settings;
|
||||
|
||||
public static class EventScriptSourceModes
|
||||
{
|
||||
public const string Path = "path";
|
||||
|
||||
public const string Inline = "inline";
|
||||
}
|
||||
|
||||
public sealed class SystemSettingsDto
|
||||
{
|
||||
public string FfmpegPath { get; set; } = "ffmpeg";
|
||||
@@ -54,12 +61,24 @@ public sealed class SystemSettingsDto
|
||||
|
||||
public bool EnableEventScripts { get; set; } = false;
|
||||
|
||||
public string LiveStartedScriptMode { get; set; } = EventScriptSourceModes.Path;
|
||||
|
||||
public string LiveStartedScriptPath { get; set; } = string.Empty;
|
||||
|
||||
public string LiveStartedScriptContent { get; set; } = string.Empty;
|
||||
|
||||
public string LiveEndedScriptMode { get; set; } = EventScriptSourceModes.Path;
|
||||
|
||||
public string LiveEndedScriptPath { get; set; } = string.Empty;
|
||||
|
||||
public string LiveEndedScriptContent { get; set; } = string.Empty;
|
||||
|
||||
public string SegmentCompletedScriptMode { get; set; } = EventScriptSourceModes.Path;
|
||||
|
||||
public string SegmentCompletedScriptPath { get; set; } = string.Empty;
|
||||
|
||||
public string SegmentCompletedScriptContent { get; set; } = string.Empty;
|
||||
|
||||
public int EventScriptTimeoutSeconds { get; set; } = 60;
|
||||
|
||||
public bool EnableEmailNotification { get; set; } = false;
|
||||
@@ -179,12 +198,24 @@ public sealed class UpdateSystemSettingsRequest
|
||||
|
||||
public bool EnableEventScripts { get; set; } = false;
|
||||
|
||||
public string LiveStartedScriptMode { get; set; } = EventScriptSourceModes.Path;
|
||||
|
||||
public string LiveStartedScriptPath { get; set; } = string.Empty;
|
||||
|
||||
public string LiveStartedScriptContent { get; set; } = string.Empty;
|
||||
|
||||
public string LiveEndedScriptMode { get; set; } = EventScriptSourceModes.Path;
|
||||
|
||||
public string LiveEndedScriptPath { get; set; } = string.Empty;
|
||||
|
||||
public string LiveEndedScriptContent { get; set; } = string.Empty;
|
||||
|
||||
public string SegmentCompletedScriptMode { get; set; } = EventScriptSourceModes.Path;
|
||||
|
||||
public string SegmentCompletedScriptPath { get; set; } = string.Empty;
|
||||
|
||||
public string SegmentCompletedScriptContent { get; set; } = string.Empty;
|
||||
|
||||
public int EventScriptTimeoutSeconds { get; set; } = 60;
|
||||
|
||||
public bool EnableEmailNotification { get; set; } = false;
|
||||
|
||||
@@ -33,9 +33,15 @@ public sealed class SystemSettingsService : ISystemSettingsService
|
||||
private const string AutoStartRecordingOnLiveKey = "scheduler.auto_start_recording_on_live";
|
||||
private const string PollingIntervalSecondsKey = "scheduler.polling_interval_seconds";
|
||||
private const string EnableEventScriptsKey = "event_scripts.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 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 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 EnableEmailNotificationKey = "notification.email.enabled";
|
||||
private const string EmailSmtpHostKey = "notification.email.smtp_host";
|
||||
@@ -103,9 +109,15 @@ public sealed class SystemSettingsService : ISystemSettingsService
|
||||
AutoStartRecordingOnLive = bool.TryParse(GetValue(lookup, AutoStartRecordingOnLiveKey, "true"), out var autoStartRecordingOnLive) && autoStartRecordingOnLive,
|
||||
PollingIntervalSeconds = GetIntValue(lookup, PollingIntervalSecondsKey, 60, 10, 3600),
|
||||
EnableEventScripts = bool.TryParse(GetValue(lookup, EnableEventScriptsKey, "false"), out var enableEventScripts) && enableEventScripts,
|
||||
LiveStartedScriptMode = GetEventScriptMode(lookup, LiveStartedScriptModeKey, LiveStartedScriptPathKey, LiveStartedScriptContentKey),
|
||||
LiveStartedScriptPath = GetValue(lookup, LiveStartedScriptPathKey, string.Empty),
|
||||
LiveStartedScriptContent = GetValue(lookup, LiveStartedScriptContentKey, string.Empty),
|
||||
LiveEndedScriptMode = GetEventScriptMode(lookup, LiveEndedScriptModeKey, LiveEndedScriptPathKey, LiveEndedScriptContentKey),
|
||||
LiveEndedScriptPath = GetValue(lookup, LiveEndedScriptPathKey, string.Empty),
|
||||
LiveEndedScriptContent = GetValue(lookup, LiveEndedScriptContentKey, string.Empty),
|
||||
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),
|
||||
EnableEmailNotification = bool.TryParse(GetValue(lookup, EnableEmailNotificationKey, "false"), out var enableEmailNotification) && enableEmailNotification,
|
||||
EmailSmtpHost = GetValue(lookup, EmailSmtpHostKey, string.Empty),
|
||||
@@ -211,9 +223,15 @@ public sealed class SystemSettingsService : ISystemSettingsService
|
||||
await UpsertAsync(AutoStartRecordingOnLiveKey, request.AutoStartRecordingOnLive.ToString(), now, cancellationToken);
|
||||
await UpsertAsync(PollingIntervalSecondsKey, request.PollingIntervalSeconds.ToString(), now, cancellationToken);
|
||||
await UpsertAsync(EnableEventScriptsKey, request.EnableEventScripts.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(LiveEndedScriptModeKey, NormalizeEventScriptMode(request.LiveEndedScriptMode), now, cancellationToken);
|
||||
await UpsertAsync(LiveEndedScriptPathKey, request.LiveEndedScriptPath.Trim(), now, cancellationToken);
|
||||
await UpsertAsync(LiveEndedScriptContentKey, request.LiveEndedScriptContent, 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(EnableEmailNotificationKey, request.EnableEmailNotification.ToString(), now, cancellationToken);
|
||||
await UpsertAsync(EmailSmtpHostKey, request.EmailSmtpHost.Trim(), now, cancellationToken);
|
||||
@@ -241,6 +259,24 @@ public sealed class SystemSettingsService : ISystemSettingsService
|
||||
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,
|
||||
@@ -257,6 +293,11 @@ public sealed class SystemSettingsService : ISystemSettingsService
|
||||
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);
|
||||
|
||||
@@ -2,6 +2,7 @@ using System.Diagnostics;
|
||||
using LiveRecorder.Application.Abstractions.Logging;
|
||||
using LiveRecorder.Application.Abstractions.Scripting;
|
||||
using LiveRecorder.Application.Abstractions.Settings;
|
||||
using LiveRecorder.Application.Models.Settings;
|
||||
using LiveRecorder.Domain.Entities;
|
||||
using LiveRecorder.Domain.Enums;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@@ -31,7 +32,9 @@ public sealed class EventScriptService : IEventScriptService
|
||||
environment["LIVE_RECORDER_EVENT"] = "live_started";
|
||||
await RunAsync(
|
||||
settings.EnableEventScripts,
|
||||
settings.LiveStartedScriptMode,
|
||||
settings.LiveStartedScriptPath,
|
||||
settings.LiveStartedScriptContent,
|
||||
settings.EventScriptTimeoutSeconds,
|
||||
"live_started",
|
||||
environment,
|
||||
@@ -48,7 +51,9 @@ public sealed class EventScriptService : IEventScriptService
|
||||
environment["LIVE_RECORDER_EVENT"] = "live_ended";
|
||||
await RunAsync(
|
||||
settings.EnableEventScripts,
|
||||
settings.LiveEndedScriptMode,
|
||||
settings.LiveEndedScriptPath,
|
||||
settings.LiveEndedScriptContent,
|
||||
settings.EventScriptTimeoutSeconds,
|
||||
"live_ended",
|
||||
environment,
|
||||
@@ -82,7 +87,9 @@ public sealed class EventScriptService : IEventScriptService
|
||||
|
||||
await RunAsync(
|
||||
settings.EnableEventScripts,
|
||||
settings.SegmentCompletedScriptMode,
|
||||
settings.SegmentCompletedScriptPath,
|
||||
settings.SegmentCompletedScriptContent,
|
||||
settings.EventScriptTimeoutSeconds,
|
||||
"segment_completed",
|
||||
environment,
|
||||
@@ -94,7 +101,9 @@ public sealed class EventScriptService : IEventScriptService
|
||||
|
||||
private async Task RunAsync(
|
||||
bool enabled,
|
||||
string scriptMode,
|
||||
string scriptPath,
|
||||
string scriptContent,
|
||||
int timeoutSeconds,
|
||||
string eventName,
|
||||
IReadOnlyDictionary<string, string> environment,
|
||||
@@ -103,19 +112,24 @@ public sealed class EventScriptService : IEventScriptService
|
||||
Guid? recordTaskId,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (!enabled || string.IsNullOrWhiteSpace(scriptPath))
|
||||
if (!enabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var normalizedScriptPath = NormalizePath(scriptPath);
|
||||
if (!File.Exists(normalizedScriptPath))
|
||||
var execution = CreateExecution(scriptMode, scriptPath, scriptContent);
|
||||
if (execution is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (execution.IsMissing)
|
||||
{
|
||||
await _systemLogService.WriteAsync(
|
||||
SystemLogLevel.Warning,
|
||||
"Script",
|
||||
$"Event script was not found for {eventName}.",
|
||||
normalizedScriptPath,
|
||||
execution.Detail,
|
||||
liveRoomId,
|
||||
recordSessionId,
|
||||
recordTaskId,
|
||||
@@ -123,7 +137,7 @@ public sealed class EventScriptService : IEventScriptService
|
||||
return;
|
||||
}
|
||||
|
||||
var startInfo = CreateStartInfo(normalizedScriptPath);
|
||||
var startInfo = execution.StartInfo!;
|
||||
foreach (var pair in environment)
|
||||
{
|
||||
startInfo.Environment[pair.Key] = pair.Value;
|
||||
@@ -148,7 +162,7 @@ public sealed class EventScriptService : IEventScriptService
|
||||
process.ExitCode == 0
|
||||
? $"Event script completed for {eventName}."
|
||||
: $"Event script exited with code {process.ExitCode} for {eventName}.",
|
||||
normalizedScriptPath,
|
||||
execution.Detail,
|
||||
liveRoomId,
|
||||
recordSessionId,
|
||||
recordTaskId,
|
||||
@@ -161,7 +175,7 @@ public sealed class EventScriptService : IEventScriptService
|
||||
SystemLogLevel.Warning,
|
||||
"Script",
|
||||
$"Event script timed out for {eventName}.",
|
||||
normalizedScriptPath,
|
||||
execution.Detail,
|
||||
liveRoomId,
|
||||
recordSessionId,
|
||||
recordTaskId,
|
||||
@@ -182,6 +196,32 @@ public sealed class EventScriptService : IEventScriptService
|
||||
}
|
||||
}
|
||||
|
||||
private static EventScriptExecution? CreateExecution(string scriptMode, string scriptPath, string scriptContent)
|
||||
{
|
||||
if (string.Equals(scriptMode, EventScriptSourceModes.Inline, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(scriptContent))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new EventScriptExecution(CreateInlineStartInfo(scriptContent), "[inline script]");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(scriptPath))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var normalizedScriptPath = NormalizePath(scriptPath);
|
||||
if (!File.Exists(normalizedScriptPath))
|
||||
{
|
||||
return new EventScriptExecution(null, normalizedScriptPath, IsMissing: true);
|
||||
}
|
||||
|
||||
return new EventScriptExecution(CreateStartInfo(normalizedScriptPath), normalizedScriptPath);
|
||||
}
|
||||
|
||||
private static Dictionary<string, string> BuildLiveRoomEnvironment(LiveRoom? liveRoom, DateTimeOffset occurredAt)
|
||||
{
|
||||
return new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||
@@ -239,6 +279,34 @@ public sealed class EventScriptService : IEventScriptService
|
||||
return startInfo;
|
||||
}
|
||||
|
||||
private static ProcessStartInfo CreateInlineStartInfo(string scriptContent)
|
||||
{
|
||||
var startInfo = new ProcessStartInfo
|
||||
{
|
||||
UseShellExecute = false,
|
||||
RedirectStandardError = false,
|
||||
RedirectStandardOutput = false,
|
||||
CreateNoWindow = true,
|
||||
WorkingDirectory = AppContext.BaseDirectory
|
||||
};
|
||||
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
startInfo.FileName = "powershell";
|
||||
startInfo.ArgumentList.Add("-NoProfile");
|
||||
startInfo.ArgumentList.Add("-ExecutionPolicy");
|
||||
startInfo.ArgumentList.Add("Bypass");
|
||||
startInfo.ArgumentList.Add("-Command");
|
||||
startInfo.ArgumentList.Add(scriptContent);
|
||||
return startInfo;
|
||||
}
|
||||
|
||||
startInfo.FileName = "/bin/sh";
|
||||
startInfo.ArgumentList.Add("-c");
|
||||
startInfo.ArgumentList.Add(scriptContent);
|
||||
return startInfo;
|
||||
}
|
||||
|
||||
private static string NormalizePath(string? path)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
@@ -275,4 +343,6 @@ public sealed class EventScriptService : IEventScriptService
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private sealed record EventScriptExecution(ProcessStartInfo? StartInfo, string Detail, bool IsMissing = false);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user