feat: expand platform adapters and preview tooling
This commit is contained in:
@@ -28,7 +28,10 @@ public sealed class EmailNotificationService : IEmailNotificationService
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task SendLiveStartedAsync(LiveRoom liveRoom, CancellationToken cancellationToken = default)
|
||||
public async Task SendLiveStartedAsync(
|
||||
LiveRoom liveRoom,
|
||||
CancellationToken cancellationToken = default,
|
||||
string? eventScriptOutput = null)
|
||||
{
|
||||
var settings = await _systemSettingsService.GetAsync(cancellationToken);
|
||||
if (!settings.EnableEmailNotification || !settings.NotifyOnLiveStarted)
|
||||
@@ -43,7 +46,8 @@ public sealed class EmailNotificationService : IEmailNotificationService
|
||||
["title"] = liveRoom.Title,
|
||||
["anchor"] = liveRoom.AnchorName,
|
||||
["sourceUrl"] = liveRoom.SourceUrl,
|
||||
["detectedAtUtc"] = ChinaTime.ToBeijingTime(DateTimeOffset.UtcNow).ToString("O")
|
||||
["detectedAtUtc"] = ChinaTime.ToBeijingTime(DateTimeOffset.UtcNow).ToString("O"),
|
||||
["eventScriptOutput"] = NormalizeNotificationText(eventScriptOutput)
|
||||
});
|
||||
|
||||
var subject = RenderSubject(settings.EmailLiveStartedSubjectTemplate, tokens);
|
||||
@@ -58,7 +62,8 @@ public sealed class EmailNotificationService : IEmailNotificationService
|
||||
string? detail = null,
|
||||
LiveRoom? liveRoom = null,
|
||||
RecordTask? recordTask = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
CancellationToken cancellationToken = default,
|
||||
string? eventScriptOutput = null)
|
||||
{
|
||||
var settings = await _systemSettingsService.GetAsync(cancellationToken);
|
||||
if (!settings.EnableEmailNotification || !settings.NotifyOnException)
|
||||
@@ -75,7 +80,8 @@ public sealed class EmailNotificationService : IEmailNotificationService
|
||||
["roomId"] = liveRoom?.RoomId,
|
||||
["recordTaskId"] = recordTask?.Id.ToString(),
|
||||
["taskStatus"] = recordTask?.Status.ToString(),
|
||||
["occurredAtUtc"] = ChinaTime.ToBeijingTime(DateTimeOffset.UtcNow).ToString("O")
|
||||
["occurredAtUtc"] = ChinaTime.ToBeijingTime(DateTimeOffset.UtcNow).ToString("O"),
|
||||
["eventScriptOutput"] = NormalizeNotificationText(eventScriptOutput)
|
||||
});
|
||||
|
||||
var subject = RenderSubject(settings.EmailExceptionSubjectTemplate, tokens);
|
||||
@@ -114,7 +120,8 @@ public sealed class EmailNotificationService : IEmailNotificationService
|
||||
["title"] = "Sample Live Title",
|
||||
["anchor"] = "Sample Anchor",
|
||||
["sourceUrl"] = "https://live.douyin.com/123456789",
|
||||
["detectedAtUtc"] = ChinaTime.ToBeijingTime(DateTimeOffset.UtcNow).ToString("O")
|
||||
["detectedAtUtc"] = ChinaTime.ToBeijingTime(DateTimeOffset.UtcNow).ToString("O"),
|
||||
["eventScriptOutput"] = "line one from script\nline two from script"
|
||||
});
|
||||
var sampleExceptionTokens = CreateTokenMap(new Dictionary<string, string?>
|
||||
{
|
||||
@@ -125,7 +132,8 @@ public sealed class EmailNotificationService : IEmailNotificationService
|
||||
["roomId"] = "123456789",
|
||||
["recordTaskId"] = Guid.NewGuid().ToString(),
|
||||
["taskStatus"] = "Running",
|
||||
["occurredAtUtc"] = ChinaTime.ToBeijingTime(DateTimeOffset.UtcNow).ToString("O")
|
||||
["occurredAtUtc"] = ChinaTime.ToBeijingTime(DateTimeOffset.UtcNow).ToString("O"),
|
||||
["eventScriptOutput"] = "line one from script\nline two from script"
|
||||
});
|
||||
|
||||
var body = $$"""
|
||||
@@ -275,6 +283,24 @@ public sealed class EmailNotificationService : IEmailNotificationService
|
||||
return tokens;
|
||||
}
|
||||
|
||||
private static string NormalizeNotificationText(string? value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
const int maxLength = 8000;
|
||||
const string suffix = "... [truncated]";
|
||||
var trimmed = value.Trim();
|
||||
if (trimmed.Length <= maxLength)
|
||||
{
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
return string.Concat(trimmed[..(maxLength - suffix.Length)], suffix);
|
||||
}
|
||||
|
||||
private static string RenderSubject(string template, IReadOnlyDictionary<string, string> tokens)
|
||||
{
|
||||
var rendered = RenderTemplate(template, tokens, htmlEncodeValues: false);
|
||||
|
||||
@@ -30,12 +30,15 @@ public sealed class EventScriptService : IEventScriptService
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task RunLiveStartedAsync(LiveRoom liveRoom, DateTimeOffset occurredAt, CancellationToken cancellationToken = default)
|
||||
public async Task<EventScriptExecutionResultDto?> RunLiveStartedAsync(
|
||||
LiveRoom liveRoom,
|
||||
DateTimeOffset occurredAt,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var settings = await _settingsService.GetAsync(cancellationToken);
|
||||
var environment = BuildLiveRoomEnvironment(liveRoom, occurredAt);
|
||||
environment["LIVE_RECORDER_EVENT"] = "live_started";
|
||||
await RunAsync(
|
||||
return await RunAsync(
|
||||
settings.EnableEventScripts && settings.EnableLiveStartedScript,
|
||||
settings.LiveStartedScriptMode,
|
||||
settings.LiveStartedScriptPath,
|
||||
@@ -50,12 +53,15 @@ public sealed class EventScriptService : IEventScriptService
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
public async Task RunLiveEndedAsync(LiveRoom liveRoom, DateTimeOffset occurredAt, CancellationToken cancellationToken = default)
|
||||
public async Task<EventScriptExecutionResultDto?> RunLiveEndedAsync(
|
||||
LiveRoom liveRoom,
|
||||
DateTimeOffset occurredAt,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var settings = await _settingsService.GetAsync(cancellationToken);
|
||||
var environment = BuildLiveRoomEnvironment(liveRoom, occurredAt);
|
||||
environment["LIVE_RECORDER_EVENT"] = "live_ended";
|
||||
await RunAsync(
|
||||
return await RunAsync(
|
||||
settings.EnableEventScripts && settings.EnableLiveEndedScript,
|
||||
settings.LiveEndedScriptMode,
|
||||
settings.LiveEndedScriptPath,
|
||||
@@ -70,7 +76,7 @@ public sealed class EventScriptService : IEventScriptService
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
public async Task RunSegmentCompletedAsync(
|
||||
public async Task<EventScriptExecutionResultDto?> RunSegmentCompletedAsync(
|
||||
LiveRoom? liveRoom,
|
||||
RecordSession recordSession,
|
||||
RecordTask recordTask,
|
||||
@@ -93,7 +99,7 @@ public sealed class EventScriptService : IEventScriptService
|
||||
environment["LIVE_RECORDER_TASK_STATUS"] = recordTask.Status.ToString();
|
||||
environment["LIVE_RECORDER_SESSION_STATUS"] = recordSession.Status.ToString();
|
||||
|
||||
await RunAsync(
|
||||
return await RunAsync(
|
||||
forceRun || (settings.EnableEventScripts && settings.EnableSegmentCompletedScript),
|
||||
settings.SegmentCompletedScriptMode,
|
||||
settings.SegmentCompletedScriptPath,
|
||||
@@ -156,7 +162,15 @@ public sealed class EventScriptService : IEventScriptService
|
||||
};
|
||||
}
|
||||
|
||||
private async Task RunAsync(
|
||||
private static EventScriptExecutionResultDto MapOutcome(ScriptExecutionOutcome outcome) => new()
|
||||
{
|
||||
Success = outcome.Success,
|
||||
Message = outcome.Message,
|
||||
Detail = outcome.Detail,
|
||||
CustomLogOutput = outcome.CustomLogOutput
|
||||
};
|
||||
|
||||
private async Task<EventScriptExecutionResultDto?> RunAsync(
|
||||
bool enabled,
|
||||
string scriptMode,
|
||||
string scriptPath,
|
||||
@@ -172,10 +186,10 @@ public sealed class EventScriptService : IEventScriptService
|
||||
{
|
||||
if (!enabled)
|
||||
{
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
|
||||
await ExecuteAsync(
|
||||
var outcome = await ExecuteAsync(
|
||||
scriptMode,
|
||||
scriptPath,
|
||||
scriptContent,
|
||||
@@ -187,6 +201,8 @@ public sealed class EventScriptService : IEventScriptService
|
||||
recordSessionId,
|
||||
recordTaskId,
|
||||
cancellationToken);
|
||||
|
||||
return MapOutcome(outcome);
|
||||
}
|
||||
|
||||
private async Task<ScriptExecutionOutcome> ExecuteAsync(
|
||||
|
||||
@@ -1401,7 +1401,20 @@ public sealed partial class FfmpegService
|
||||
}
|
||||
|
||||
includeNonChatEvents = runtime.RecordingSettings.DanmakuIncludeNonChatEvents;
|
||||
var adapter = adapterFactory.GetByPlatform(liveRoom.Platform);
|
||||
var adapter = adapterFactory.TryGetByPlatform(liveRoom.Platform);
|
||||
if (adapter is null)
|
||||
{
|
||||
await WriteDanmakuSystemLogAsync(
|
||||
SystemLogLevel.Info,
|
||||
$"No danmaku adapter is registered for {liveRoom.Platform}. Recording will continue without live comments.",
|
||||
null,
|
||||
runtime.LiveRoomId,
|
||||
runtime.RecordSessionId,
|
||||
initialTask.Id,
|
||||
cancellationToken);
|
||||
return;
|
||||
}
|
||||
|
||||
runtime.DanmakuConnection = await adapter.ConnectAsync(
|
||||
new DanmakuConnectionContext(
|
||||
liveRoom.Id,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Net;
|
||||
using System.Net.Security;
|
||||
using System.Security.Authentication;
|
||||
using LiveRecorder.Application.Common;
|
||||
using LiveRecorder.Application.Abstractions.Settings;
|
||||
using LiveRecorder.Application.Models.Settings;
|
||||
using LiveRecorder.Domain.Enums;
|
||||
@@ -54,15 +55,14 @@ public sealed class PlatformHttpClientFactory
|
||||
|
||||
private static IWebProxy? BuildProxy(LivePlatformType platform, SystemSettingsDto settings)
|
||||
{
|
||||
var proxySettings = platform switch
|
||||
if (!LivePlatformCatalog.TryGet(platform, out _))
|
||||
{
|
||||
LivePlatformType.Douyin => settings.DouyinProxy,
|
||||
LivePlatformType.Bilibili => settings.BilibiliProxy,
|
||||
LivePlatformType.Huya => settings.HuyaProxy,
|
||||
_ => null
|
||||
};
|
||||
return null;
|
||||
}
|
||||
|
||||
if (proxySettings is null || !proxySettings.Enabled || string.IsNullOrWhiteSpace(proxySettings.ProxyUrl))
|
||||
var proxySettings = settings.GetPlatformRequestSettings(platform).Proxy;
|
||||
|
||||
if (!proxySettings.Enabled || string.IsNullOrWhiteSpace(proxySettings.ProxyUrl))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -34,7 +34,10 @@ public sealed class WebhookNotificationService : IWebhookNotificationService
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task SendLiveStartedAsync(LiveRoom liveRoom, CancellationToken cancellationToken = default)
|
||||
public async Task SendLiveStartedAsync(
|
||||
LiveRoom liveRoom,
|
||||
CancellationToken cancellationToken = default,
|
||||
string? eventScriptOutput = null)
|
||||
{
|
||||
var settings = await _systemSettingsService.GetAsync(cancellationToken);
|
||||
if (!settings.EnableWebhookNotification || !settings.NotifyWebhookOnLiveStarted)
|
||||
@@ -49,7 +52,8 @@ public sealed class WebhookNotificationService : IWebhookNotificationService
|
||||
source: "LiveRoomStatus",
|
||||
liveRoom: liveRoom,
|
||||
recordTask: null,
|
||||
report: null);
|
||||
report: null,
|
||||
eventScriptOutput: eventScriptOutput);
|
||||
var variables = BuildTemplateVariables(payload, report: null);
|
||||
|
||||
await SendInternalAsync(
|
||||
@@ -66,7 +70,8 @@ public sealed class WebhookNotificationService : IWebhookNotificationService
|
||||
string? detail = null,
|
||||
LiveRoom? liveRoom = null,
|
||||
RecordTask? recordTask = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
CancellationToken cancellationToken = default,
|
||||
string? eventScriptOutput = null)
|
||||
{
|
||||
var settings = await _systemSettingsService.GetAsync(cancellationToken);
|
||||
if (!settings.EnableWebhookNotification || !settings.NotifyWebhookOnException)
|
||||
@@ -81,7 +86,8 @@ public sealed class WebhookNotificationService : IWebhookNotificationService
|
||||
source,
|
||||
liveRoom,
|
||||
recordTask,
|
||||
report: null);
|
||||
report: null,
|
||||
eventScriptOutput: eventScriptOutput);
|
||||
var variables = BuildTemplateVariables(payload, report: null);
|
||||
|
||||
await SendInternalAsync(
|
||||
@@ -111,7 +117,8 @@ public sealed class WebhookNotificationService : IWebhookNotificationService
|
||||
"DailyReview",
|
||||
liveRoom: null,
|
||||
recordTask: null,
|
||||
report);
|
||||
report: report,
|
||||
eventScriptOutput: null);
|
||||
var variables = BuildTemplateVariables(payload, report);
|
||||
|
||||
await SendInternalAsync(
|
||||
@@ -160,7 +167,8 @@ public sealed class WebhookNotificationService : IWebhookNotificationService
|
||||
"SettingsTest",
|
||||
sampleLiveRoom,
|
||||
recordTask: null,
|
||||
report: null);
|
||||
report: null,
|
||||
eventScriptOutput: "line one from script\nline two from script");
|
||||
var variables = BuildTemplateVariables(payload, report: null);
|
||||
|
||||
try
|
||||
@@ -339,7 +347,8 @@ public sealed class WebhookNotificationService : IWebhookNotificationService
|
||||
string source,
|
||||
LiveRoom? liveRoom,
|
||||
RecordTask? recordTask,
|
||||
DailyReviewReportDto? report)
|
||||
DailyReviewReportDto? report,
|
||||
string? eventScriptOutput)
|
||||
{
|
||||
var payload = new Dictionary<string, object?>
|
||||
{
|
||||
@@ -349,6 +358,7 @@ public sealed class WebhookNotificationService : IWebhookNotificationService
|
||||
["summary"] = summary,
|
||||
["detail"] = detail,
|
||||
["source"] = source,
|
||||
["eventScriptOutput"] = NormalizeNotificationText(eventScriptOutput),
|
||||
["liveRoom"] = liveRoom is null ? null : new Dictionary<string, object?>
|
||||
{
|
||||
["id"] = liveRoom.Id,
|
||||
@@ -403,6 +413,11 @@ public sealed class WebhookNotificationService : IWebhookNotificationService
|
||||
["source"] = payload["source"]
|
||||
};
|
||||
|
||||
if (payload.TryGetValue("eventScriptOutput", out var eventScriptOutput))
|
||||
{
|
||||
variables["eventScriptOutput"] = eventScriptOutput;
|
||||
}
|
||||
|
||||
if (payload.TryGetValue("liveRoom", out var liveRoomPayload) &&
|
||||
liveRoomPayload is IReadOnlyDictionary<string, object?> liveRoom)
|
||||
{
|
||||
@@ -436,6 +451,24 @@ public sealed class WebhookNotificationService : IWebhookNotificationService
|
||||
return variables;
|
||||
}
|
||||
|
||||
private static string NormalizeNotificationText(string? value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
const int maxLength = 8000;
|
||||
const string suffix = "... [truncated]";
|
||||
var trimmed = value.Trim();
|
||||
if (trimmed.Length <= maxLength)
|
||||
{
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
return string.Concat(trimmed[..(maxLength - suffix.Length)], suffix);
|
||||
}
|
||||
|
||||
private static string Truncate(string? value, int maxLength)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
|
||||
Reference in New Issue
Block a user