feat: add import detection toggles
This commit is contained in:
@@ -53,6 +53,13 @@ export interface ImportLiveRoomsResult {
|
|||||||
items: ImportLiveRoomItemResult[];
|
items: ImportLiveRoomItemResult[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ImportLiveRoomsRequest {
|
||||||
|
content: string;
|
||||||
|
platformOverride?: number | null;
|
||||||
|
detectRoomMetadata: boolean;
|
||||||
|
detectLiveStatus: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ImportLiveRoomItemResult {
|
export interface ImportLiveRoomItemResult {
|
||||||
lineNumber: number;
|
lineNumber: number;
|
||||||
rawLine: string;
|
rawLine: string;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { computed, nextTick, onBeforeUnmount, onMounted, reactive, ref } from "v
|
|||||||
import { ElMessage } from "element-plus";
|
import { ElMessage } from "element-plus";
|
||||||
import apiClient, { getApiErrorMessage } from "@/api/client";
|
import apiClient, { getApiErrorMessage } from "@/api/client";
|
||||||
import { useViewport } from "@/composables/useViewport";
|
import { useViewport } from "@/composables/useViewport";
|
||||||
import type { BatchLiveRoomsResult, ImportLiveRoomsResult, LiveRoom, RecordTask } from "@/types";
|
import type { BatchLiveRoomsResult, ImportLiveRoomsRequest, ImportLiveRoomsResult, LiveRoom, RecordTask } from "@/types";
|
||||||
import {
|
import {
|
||||||
autoStartDecisionLabelMap,
|
autoStartDecisionLabelMap,
|
||||||
availabilityLabelMap,
|
availabilityLabelMap,
|
||||||
@@ -49,7 +49,9 @@ const createForm = reactive({
|
|||||||
|
|
||||||
const importForm = reactive({
|
const importForm = reactive({
|
||||||
content: "",
|
content: "",
|
||||||
platformOverride: null as number | null
|
platformOverride: null as number | null,
|
||||||
|
detectRoomMetadata: true,
|
||||||
|
detectLiveStatus: true
|
||||||
});
|
});
|
||||||
|
|
||||||
const importResult = ref<ImportLiveRoomsResult | null>(null);
|
const importResult = ref<ImportLiveRoomsResult | null>(null);
|
||||||
@@ -331,6 +333,8 @@ function resetCreateForm() {
|
|||||||
function resetImportForm() {
|
function resetImportForm() {
|
||||||
importForm.content = "";
|
importForm.content = "";
|
||||||
importForm.platformOverride = null;
|
importForm.platformOverride = null;
|
||||||
|
importForm.detectRoomMetadata = true;
|
||||||
|
importForm.detectLiveStatus = true;
|
||||||
importResult.value = null;
|
importResult.value = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -367,10 +371,14 @@ async function importRooms() {
|
|||||||
importResult.value = null;
|
importResult.value = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { data } = await apiClient.post<ImportLiveRoomsResult>("/live-rooms/import", {
|
const payload: ImportLiveRoomsRequest = {
|
||||||
content: importForm.content,
|
content: importForm.content,
|
||||||
platformOverride: importForm.platformOverride
|
platformOverride: importForm.platformOverride,
|
||||||
});
|
detectRoomMetadata: importForm.detectRoomMetadata,
|
||||||
|
detectLiveStatus: importForm.detectLiveStatus
|
||||||
|
};
|
||||||
|
|
||||||
|
const { data } = await apiClient.post<ImportLiveRoomsResult>("/live-rooms/import", payload);
|
||||||
|
|
||||||
importResult.value = data;
|
importResult.value = data;
|
||||||
ElMessage.success(`导入完成:成功 ${data.successCount} 条,失败 ${data.failedCount} 条。`);
|
ElMessage.success(`导入完成:成功 ${data.successCount} 条,失败 ${data.failedCount} 条。`);
|
||||||
@@ -1098,6 +1106,31 @@ onBeforeUnmount(() => {
|
|||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
|
|
||||||
|
<div class="import-options">
|
||||||
|
<div class="settings-toggle-item import-options__item">
|
||||||
|
<div>
|
||||||
|
<div class="settings-toggle-item__label">检测直播间信息</div>
|
||||||
|
<div class="settings-toggle-item__hint">关闭后不在导入时拉取标题、头像等信息。</div>
|
||||||
|
</div>
|
||||||
|
<el-switch v-model="importForm.detectRoomMetadata" />
|
||||||
|
</div>
|
||||||
|
<div class="settings-toggle-item import-options__item">
|
||||||
|
<div>
|
||||||
|
<div class="settings-toggle-item__label">检测开播状态</div>
|
||||||
|
<div class="settings-toggle-item__hint">关闭后不在导入时请求开播状态,也不会立即触发自动开录。</div>
|
||||||
|
</div>
|
||||||
|
<el-switch v-model="importForm.detectLiveStatus" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-alert
|
||||||
|
v-if="!importForm.detectRoomMetadata && !importForm.detectLiveStatus"
|
||||||
|
type="warning"
|
||||||
|
:closable="false"
|
||||||
|
show-icon
|
||||||
|
title="当前将只做本地解析并直接入库,不请求远端链接。适合一次导入很多直播间时使用。"
|
||||||
|
/>
|
||||||
|
|
||||||
<div v-if="importResult" class="import-result">
|
<div v-if="importResult" class="import-result">
|
||||||
<div class="import-result__summary">
|
<div class="import-result__summary">
|
||||||
共 {{ importResult.totalCount }} 条,成功 {{ importResult.successCount }} 条,新增
|
共 {{ importResult.totalCount }} 条,成功 {{ importResult.successCount }} 条,新增
|
||||||
@@ -1594,6 +1627,13 @@ onBeforeUnmount(() => {
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.settings-toggle-item__hint {
|
||||||
|
margin-top: 4px;
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
.dialog-form {
|
.dialog-form {
|
||||||
display: grid;
|
display: grid;
|
||||||
}
|
}
|
||||||
@@ -1602,6 +1642,15 @@ onBeforeUnmount(() => {
|
|||||||
gap: 16px;
|
gap: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.import-options {
|
||||||
|
display: grid;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.import-options__item {
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
.import-result {
|
.import-result {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
|
|||||||
@@ -16,6 +16,10 @@ public sealed class ImportLiveRoomsRequest
|
|||||||
public string Content { get; set; } = string.Empty;
|
public string Content { get; set; } = string.Empty;
|
||||||
|
|
||||||
public LivePlatformType? PlatformOverride { get; set; }
|
public LivePlatformType? PlatformOverride { get; set; }
|
||||||
|
|
||||||
|
public bool DetectRoomMetadata { get; set; } = true;
|
||||||
|
|
||||||
|
public bool DetectLiveStatus { get; set; } = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class ImportLiveRoomsResultDto
|
public sealed class ImportLiveRoomsResultDto
|
||||||
|
|||||||
@@ -8,11 +8,20 @@ using LiveRecorder.Application.Models.LiveRooms;
|
|||||||
using LiveRecorder.Application.Models.RecordTasks;
|
using LiveRecorder.Application.Models.RecordTasks;
|
||||||
using LiveRecorder.Domain.Entities;
|
using LiveRecorder.Domain.Entities;
|
||||||
using LiveRecorder.Domain.Enums;
|
using LiveRecorder.Domain.Enums;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
namespace LiveRecorder.Application.Services;
|
namespace LiveRecorder.Application.Services;
|
||||||
|
|
||||||
public sealed class LiveRoomService
|
public sealed class LiveRoomService
|
||||||
{
|
{
|
||||||
|
private static readonly Regex DouyinRoomIdRegex = new(
|
||||||
|
@"(?<id>\d{8,})",
|
||||||
|
RegexOptions.Compiled | RegexOptions.CultureInvariant);
|
||||||
|
|
||||||
|
private static readonly Regex BilibiliRoomIdRegex = new(
|
||||||
|
"""^(?:(?:https?:\/\/)?live\.bilibili\.com\/(?:blanc\/|h5\/)?)?(?<id>\d+)\/?(?:[#\?].*)?$""",
|
||||||
|
RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
|
||||||
|
|
||||||
private readonly ILiveRoomRepository _liveRoomRepository;
|
private readonly ILiveRoomRepository _liveRoomRepository;
|
||||||
private readonly IRecordSessionRepository _recordSessionRepository;
|
private readonly IRecordSessionRepository _recordSessionRepository;
|
||||||
private readonly ILivePlatformAdapterFactory _livePlatformAdapterFactory;
|
private readonly ILivePlatformAdapterFactory _livePlatformAdapterFactory;
|
||||||
@@ -83,7 +92,7 @@ public sealed class LiveRoomService
|
|||||||
request.Url,
|
request.Url,
|
||||||
request.PlatformOverride,
|
request.PlatformOverride,
|
||||||
request.AnchorName,
|
request.AnchorName,
|
||||||
cancellationToken);
|
cancellationToken: cancellationToken);
|
||||||
|
|
||||||
var hasActiveSession = await _recordSessionRepository.GetActiveByLiveRoomIdAsync(liveRoom.Id, cancellationToken) is not null;
|
var hasActiveSession = await _recordSessionRepository.GetActiveByLiveRoomIdAsync(liveRoom.Id, cancellationToken) is not null;
|
||||||
return Map(liveRoom, effectiveSettings, hasActiveSession);
|
return Map(liveRoom, effectiveSettings, hasActiveSession);
|
||||||
@@ -95,6 +104,9 @@ public sealed class LiveRoomService
|
|||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(request);
|
ArgumentNullException.ThrowIfNull(request);
|
||||||
|
|
||||||
|
var detectRoomMetadata = request.DetectRoomMetadata;
|
||||||
|
var detectLiveStatus = request.DetectLiveStatus;
|
||||||
|
|
||||||
var results = new List<ImportLiveRoomItemResultDto>();
|
var results = new List<ImportLiveRoomItemResultDto>();
|
||||||
var lines = request.Content
|
var lines = request.Content
|
||||||
.Replace("\r\n", "\n", StringComparison.Ordinal)
|
.Replace("\r\n", "\n", StringComparison.Ordinal)
|
||||||
@@ -129,7 +141,9 @@ public sealed class LiveRoomService
|
|||||||
url,
|
url,
|
||||||
request.PlatformOverride,
|
request.PlatformOverride,
|
||||||
anchorName,
|
anchorName,
|
||||||
cancellationToken);
|
detectRoomMetadata,
|
||||||
|
detectLiveStatus,
|
||||||
|
cancellationToken: cancellationToken);
|
||||||
|
|
||||||
results.Add(new ImportLiveRoomItemResultDto
|
results.Add(new ImportLiveRoomItemResultDto
|
||||||
{
|
{
|
||||||
@@ -190,7 +204,9 @@ public sealed class LiveRoomService
|
|||||||
string rawInput,
|
string rawInput,
|
||||||
LivePlatformType? platformOverride,
|
LivePlatformType? platformOverride,
|
||||||
string? fallbackAnchorName,
|
string? fallbackAnchorName,
|
||||||
CancellationToken cancellationToken)
|
bool detectRoomMetadata = true,
|
||||||
|
bool detectLiveStatus = true,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
var input = rawInput.Trim();
|
var input = rawInput.Trim();
|
||||||
if (string.IsNullOrWhiteSpace(input))
|
if (string.IsNullOrWhiteSpace(input))
|
||||||
@@ -198,12 +214,30 @@ public sealed class LiveRoomService
|
|||||||
throw new InvalidOperationException("Live room URL is required.");
|
throw new InvalidOperationException("Live room URL is required.");
|
||||||
}
|
}
|
||||||
|
|
||||||
var adapter = platformOverride.HasValue && platformOverride.Value != LivePlatformType.Unknown
|
ILivePlatformAdapter? adapter = null;
|
||||||
? _livePlatformAdapterFactory.GetByPlatform(platformOverride.Value)
|
ParsedLiveRoom parsedRoom;
|
||||||
: _livePlatformAdapterFactory.GetByInput(input);
|
if (detectRoomMetadata || detectLiveStatus)
|
||||||
|
{
|
||||||
|
adapter = platformOverride.HasValue && platformOverride.Value != LivePlatformType.Unknown
|
||||||
|
? _livePlatformAdapterFactory.GetByPlatform(platformOverride.Value)
|
||||||
|
: _livePlatformAdapterFactory.GetByInput(input);
|
||||||
|
|
||||||
|
parsedRoom = await adapter.ParseRoomAsync(input, cancellationToken);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
parsedRoom = ParseRoomLocally(input, platformOverride);
|
||||||
|
}
|
||||||
|
|
||||||
|
LiveStatusSnapshot? liveStatus = null;
|
||||||
|
if (detectLiveStatus)
|
||||||
|
{
|
||||||
|
adapter ??= platformOverride.HasValue && platformOverride.Value != LivePlatformType.Unknown
|
||||||
|
? _livePlatformAdapterFactory.GetByPlatform(platformOverride.Value)
|
||||||
|
: _livePlatformAdapterFactory.GetByInput(input);
|
||||||
|
liveStatus = await adapter.GetLiveStatusAsync(parsedRoom.RoomId, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
var parsedRoom = await adapter.ParseRoomAsync(input, cancellationToken);
|
|
||||||
var liveStatus = await adapter.GetLiveStatusAsync(parsedRoom.RoomId, cancellationToken);
|
|
||||||
var now = DateTimeOffset.UtcNow;
|
var now = DateTimeOffset.UtcNow;
|
||||||
|
|
||||||
var liveRoom = await _liveRoomRepository.GetByPlatformRoomIdAsync(parsedRoom.PlatformType, parsedRoom.RoomId, cancellationToken);
|
var liveRoom = await _liveRoomRepository.GetByPlatformRoomIdAsync(parsedRoom.PlatformType, parsedRoom.RoomId, cancellationToken);
|
||||||
@@ -211,7 +245,16 @@ public sealed class LiveRoomService
|
|||||||
if (liveRoom is null)
|
if (liveRoom is null)
|
||||||
{
|
{
|
||||||
liveRoom = new LiveRoom(parsedRoom.PlatformType, parsedRoom.SourceUrl, parsedRoom.RoomId, parsedRoom.NormalizedUrl, now);
|
liveRoom = new LiveRoom(parsedRoom.PlatformType, parsedRoom.SourceUrl, parsedRoom.RoomId, parsedRoom.NormalizedUrl, now);
|
||||||
await _liveRoomStatusService.ApplySnapshotAsync(liveRoom, liveStatus, now, cancellationToken);
|
if (liveStatus is not null)
|
||||||
|
{
|
||||||
|
await _liveRoomStatusService.ApplySnapshotAsync(
|
||||||
|
liveRoom,
|
||||||
|
liveStatus,
|
||||||
|
now,
|
||||||
|
updateMetadata: detectRoomMetadata,
|
||||||
|
cancellationToken: cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
liveRoom.UpdateMetadata(null, fallbackAnchorName, null, null, null, now);
|
liveRoom.UpdateMetadata(null, fallbackAnchorName, null, null, null, now);
|
||||||
|
|
||||||
await _liveRoomRepository.AddAsync(liveRoom, cancellationToken);
|
await _liveRoomRepository.AddAsync(liveRoom, cancellationToken);
|
||||||
@@ -220,7 +263,16 @@ public sealed class LiveRoomService
|
|||||||
{
|
{
|
||||||
liveRoom.UpdateSource(parsedRoom.SourceUrl, parsedRoom.NormalizedUrl, now);
|
liveRoom.UpdateSource(parsedRoom.SourceUrl, parsedRoom.NormalizedUrl, now);
|
||||||
liveRoom.UpdateRoomId(parsedRoom.RoomId, now);
|
liveRoom.UpdateRoomId(parsedRoom.RoomId, now);
|
||||||
await _liveRoomStatusService.ApplySnapshotAsync(liveRoom, liveStatus, now, cancellationToken);
|
if (liveStatus is not null)
|
||||||
|
{
|
||||||
|
await _liveRoomStatusService.ApplySnapshotAsync(
|
||||||
|
liveRoom,
|
||||||
|
liveStatus,
|
||||||
|
now,
|
||||||
|
updateMetadata: detectRoomMetadata,
|
||||||
|
cancellationToken: cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
liveRoom.UpdateMetadata(null, fallbackAnchorName, null, null, null, now);
|
liveRoom.UpdateMetadata(null, fallbackAnchorName, null, null, null, now);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -231,7 +283,10 @@ public sealed class LiveRoomService
|
|||||||
$"Live room resolved: {liveRoom.RoomId} ({liveRoom.Platform}).",
|
$"Live room resolved: {liveRoom.RoomId} ({liveRoom.Platform}).",
|
||||||
liveRoomId: liveRoom.Id,
|
liveRoomId: liveRoom.Id,
|
||||||
cancellationToken: cancellationToken);
|
cancellationToken: cancellationToken);
|
||||||
await TryAutoStartRecordingAsync(liveRoom, liveStatus, cancellationToken);
|
if (liveStatus is not null)
|
||||||
|
{
|
||||||
|
await TryAutoStartRecordingAsync(liveRoom, liveStatus, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
var effectiveSettings = await _liveRoomRecordingSettingsResolver.ResolveAsync(liveRoom, cancellationToken);
|
var effectiveSettings = await _liveRoomRecordingSettingsResolver.ResolveAsync(liveRoom, cancellationToken);
|
||||||
return (liveRoom, effectiveSettings, created);
|
return (liveRoom, effectiveSettings, created);
|
||||||
@@ -246,7 +301,7 @@ public sealed class LiveRoomService
|
|||||||
var liveStatus = await adapter.GetLiveStatusAsync(room.RoomId, cancellationToken);
|
var liveStatus = await adapter.GetLiveStatusAsync(room.RoomId, cancellationToken);
|
||||||
var now = DateTimeOffset.UtcNow;
|
var now = DateTimeOffset.UtcNow;
|
||||||
|
|
||||||
await _liveRoomStatusService.ApplySnapshotAsync(room, liveStatus, now, cancellationToken);
|
await _liveRoomStatusService.ApplySnapshotAsync(room, liveStatus, now, cancellationToken: cancellationToken);
|
||||||
|
|
||||||
await _unitOfWork.SaveChangesAsync(cancellationToken);
|
await _unitOfWork.SaveChangesAsync(cancellationToken);
|
||||||
await _systemLogService.WriteAsync(
|
await _systemLogService.WriteAsync(
|
||||||
@@ -445,6 +500,99 @@ public sealed class LiveRoomService
|
|||||||
return BuildBatchResult(request.LiveRoomIds.Count, results);
|
return BuildBatchResult(request.LiveRoomIds.Count, results);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static ParsedLiveRoom ParseRoomLocally(string input, LivePlatformType? platformOverride)
|
||||||
|
{
|
||||||
|
var platform = ResolvePlatformForLocalImport(input, platformOverride);
|
||||||
|
return platform switch
|
||||||
|
{
|
||||||
|
LivePlatformType.Douyin => ParseDouyinRoomLocally(input),
|
||||||
|
LivePlatformType.Bilibili => ParseBilibiliRoomLocally(input),
|
||||||
|
_ => throw new NotSupportedException(
|
||||||
|
"This platform still requires live room detection during import. Please enable detection or choose a supported platform.")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static LivePlatformType ResolvePlatformForLocalImport(string input, LivePlatformType? platformOverride)
|
||||||
|
{
|
||||||
|
if (platformOverride.HasValue && platformOverride.Value != LivePlatformType.Unknown)
|
||||||
|
{
|
||||||
|
return platformOverride.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input.Contains("douyin.com", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
input.Contains("iesdouyin.com", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return LivePlatformType.Douyin;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input.Contains("live.bilibili.com", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return LivePlatformType.Bilibili;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new NotSupportedException(
|
||||||
|
"Unable to infer the platform without detection. Please specify a platform override or keep detection enabled.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ParsedLiveRoom ParseDouyinRoomLocally(string input)
|
||||||
|
{
|
||||||
|
var roomId = ExtractDouyinRoomId(input);
|
||||||
|
if (string.IsNullOrWhiteSpace(roomId))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
"Unable to extract the Douyin room id locally. Please keep detection enabled for this link.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ParsedLiveRoom(
|
||||||
|
LivePlatformType.Douyin,
|
||||||
|
roomId,
|
||||||
|
input.Trim(),
|
||||||
|
$"https://live.douyin.com/{roomId}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ParsedLiveRoom ParseBilibiliRoomLocally(string input)
|
||||||
|
{
|
||||||
|
var roomId = ExtractBilibiliRoomId(input);
|
||||||
|
if (string.IsNullOrWhiteSpace(roomId))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
"Unable to extract the Bilibili room id locally. Please keep detection enabled for this link.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ParsedLiveRoom(
|
||||||
|
LivePlatformType.Bilibili,
|
||||||
|
roomId,
|
||||||
|
input.Trim(),
|
||||||
|
$"https://live.bilibili.com/{roomId}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string? ExtractDouyinRoomId(string input)
|
||||||
|
{
|
||||||
|
var trimmedInput = input.Trim();
|
||||||
|
var match = DouyinRoomIdRegex.Match(trimmedInput);
|
||||||
|
return match.Success ? match.Groups["id"].Value : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string? ExtractBilibiliRoomId(string input)
|
||||||
|
{
|
||||||
|
var trimmedInput = input.Trim();
|
||||||
|
var directMatch = BilibiliRoomIdRegex.Match(trimmedInput);
|
||||||
|
if (directMatch.Success)
|
||||||
|
{
|
||||||
|
return directMatch.Groups["id"].Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Uri.TryCreate(trimmedInput, UriKind.Absolute, out var uri) ||
|
||||||
|
!uri.Host.Contains("live.bilibili.com", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return uri.Segments
|
||||||
|
.Select(static item => item.Trim('/'))
|
||||||
|
.FirstOrDefault(static item => !string.IsNullOrWhiteSpace(item) && item.All(char.IsDigit));
|
||||||
|
}
|
||||||
|
|
||||||
private async Task<Dictionary<Guid, RecordingExecutionSettings>> BuildEffectiveSettingsLookupAsync(
|
private async Task<Dictionary<Guid, RecordingExecutionSettings>> BuildEffectiveSettingsLookupAsync(
|
||||||
IReadOnlyCollection<LiveRoom> rooms,
|
IReadOnlyCollection<LiveRoom> rooms,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ public sealed class LiveRoomStatusService
|
|||||||
LiveRoom liveRoom,
|
LiveRoom liveRoom,
|
||||||
LiveStatusSnapshot liveStatus,
|
LiveStatusSnapshot liveStatus,
|
||||||
DateTimeOffset observedAt,
|
DateTimeOffset observedAt,
|
||||||
|
bool updateMetadata = true,
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(liveRoom);
|
ArgumentNullException.ThrowIfNull(liveRoom);
|
||||||
@@ -33,13 +34,17 @@ public sealed class LiveRoomStatusService
|
|||||||
|
|
||||||
var wasLive = liveRoom.AvailabilityStatus == LiveRoomAvailabilityStatus.Live;
|
var wasLive = liveRoom.AvailabilityStatus == LiveRoomAvailabilityStatus.Live;
|
||||||
|
|
||||||
liveRoom.UpdateMetadata(
|
if (updateMetadata)
|
||||||
liveStatus.Title,
|
{
|
||||||
liveStatus.AnchorName,
|
liveRoom.UpdateMetadata(
|
||||||
liveStatus.AnchorId,
|
liveStatus.Title,
|
||||||
liveStatus.AvatarUrl,
|
liveStatus.AnchorName,
|
||||||
liveStatus.CoverUrl,
|
liveStatus.AnchorId,
|
||||||
observedAt);
|
liveStatus.AvatarUrl,
|
||||||
|
liveStatus.CoverUrl,
|
||||||
|
observedAt);
|
||||||
|
}
|
||||||
|
|
||||||
liveRoom.UpdateAvailability(
|
liveRoom.UpdateAvailability(
|
||||||
liveStatus.IsLive ? LiveRoomAvailabilityStatus.Live : LiveRoomAvailabilityStatus.Offline,
|
liveStatus.IsLive ? LiveRoomAvailabilityStatus.Live : LiveRoomAvailabilityStatus.Offline,
|
||||||
observedAt);
|
observedAt);
|
||||||
|
|||||||
@@ -247,7 +247,7 @@ public sealed class RecordService
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var liveStatus = await adapter.GetLiveStatusAsync(liveRoom.RoomId, cancellationToken);
|
var liveStatus = await adapter.GetLiveStatusAsync(liveRoom.RoomId, cancellationToken);
|
||||||
await _liveRoomStatusService.ApplySnapshotAsync(liveRoom, liveStatus, now, cancellationToken);
|
await _liveRoomStatusService.ApplySnapshotAsync(liveRoom, liveStatus, now, cancellationToken: cancellationToken);
|
||||||
|
|
||||||
if (!liveStatus.IsLive)
|
if (!liveStatus.IsLive)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -292,7 +292,7 @@ public sealed class LiveRoomPollingBackgroundService : BackgroundService, ILiveR
|
|||||||
var liveStatus = await adapter.GetLiveStatusAsync(liveRoom.RoomId, cancellationToken);
|
var liveStatus = await adapter.GetLiveStatusAsync(liveRoom.RoomId, cancellationToken);
|
||||||
var now = DateTimeOffset.UtcNow;
|
var now = DateTimeOffset.UtcNow;
|
||||||
|
|
||||||
await liveRoomStatusService.ApplySnapshotAsync(liveRoom, liveStatus, now, cancellationToken);
|
await liveRoomStatusService.ApplySnapshotAsync(liveRoom, liveStatus, now, cancellationToken: cancellationToken);
|
||||||
await SaveChangesWithRetryAsync(dbContext, cancellationToken);
|
await SaveChangesWithRetryAsync(dbContext, cancellationToken);
|
||||||
|
|
||||||
if (!liveStatus.IsLive)
|
if (!liveStatus.IsLive)
|
||||||
|
|||||||
Reference in New Issue
Block a user