243 lines
9.8 KiB
C#
243 lines
9.8 KiB
C#
using System.Net;
|
|
using System.Net.Security;
|
|
using System.Security.Authentication;
|
|
using LiveRecorder.Application.Abstractions.Auth;
|
|
using LiveRecorder.Application.Abstractions.Logging;
|
|
using LiveRecorder.Application.Abstractions.Notifications;
|
|
using LiveRecorder.Application.Abstractions.Persistence;
|
|
using LiveRecorder.Application.Abstractions.Platforms;
|
|
using LiveRecorder.Application.Abstractions.Recording;
|
|
using LiveRecorder.Application.Abstractions.Scripting;
|
|
using LiveRecorder.Application.Abstractions.Settings;
|
|
using LiveRecorder.Application.Abstractions.Storage;
|
|
using LiveRecorder.Application.Services;
|
|
using LiveRecorder.Infrastructure.Persistence;
|
|
using LiveRecorder.Infrastructure.Persistence.Repositories;
|
|
using LiveRecorder.Infrastructure.Platforms.Bilibili;
|
|
using LiveRecorder.Infrastructure.Platforms.Bilibili.Danmaku;
|
|
using LiveRecorder.Infrastructure.Platforms.Douyin;
|
|
using LiveRecorder.Infrastructure.Platforms.Douyin.Danmaku;
|
|
using LiveRecorder.Infrastructure.Platforms.Douyin.Signing;
|
|
using LiveRecorder.Infrastructure.Platforms.Huya;
|
|
using LiveRecorder.Infrastructure.Services;
|
|
using LiveRecorder.WebApi.Middleware;
|
|
using Microsoft.Data.Sqlite;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.OpenApi.Models;
|
|
|
|
var builder = WebApplication.CreateBuilder(args);
|
|
var resetRecordingData = args.Contains("--reset-recording-data", StringComparer.OrdinalIgnoreCase);
|
|
var corsOrigins = builder.Configuration.GetSection("Cors:Origins").Get<string[]>() ?? ["http://localhost:5173"];
|
|
|
|
builder.Services.AddControllers();
|
|
builder.Services.AddMemoryCache();
|
|
builder.Services.AddEndpointsApiExplorer();
|
|
builder.Services.AddSwaggerGen(options =>
|
|
{
|
|
options.SwaggerDoc("v1", new OpenApiInfo
|
|
{
|
|
Title = "Live Recorder API",
|
|
Version = "v1",
|
|
Description = "Multi-platform live recording service"
|
|
});
|
|
|
|
options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
|
|
{
|
|
In = ParameterLocation.Header,
|
|
Description = "Input token as: Bearer {token}",
|
|
Name = "Authorization",
|
|
Type = SecuritySchemeType.ApiKey
|
|
});
|
|
|
|
options.AddSecurityRequirement(new OpenApiSecurityRequirement
|
|
{
|
|
{
|
|
new OpenApiSecurityScheme
|
|
{
|
|
Reference = new OpenApiReference
|
|
{
|
|
Type = ReferenceType.SecurityScheme,
|
|
Id = "Bearer"
|
|
}
|
|
},
|
|
Array.Empty<string>()
|
|
}
|
|
});
|
|
});
|
|
|
|
builder.Services.AddCors(options =>
|
|
{
|
|
options.AddPolicy("frontend", policy =>
|
|
{
|
|
policy.WithOrigins(corsOrigins)
|
|
.AllowAnyHeader()
|
|
.AllowAnyMethod();
|
|
});
|
|
});
|
|
|
|
builder.Services.AddHttpClient("douyin", client =>
|
|
{
|
|
client.Timeout = TimeSpan.FromSeconds(20);
|
|
client.DefaultRequestVersion = HttpVersion.Version11;
|
|
client.DefaultVersionPolicy = HttpVersionPolicy.RequestVersionOrLower;
|
|
})
|
|
.ConfigurePrimaryHttpMessageHandler(() => CreateDouyinHttpHandler(useProxy: true));
|
|
|
|
builder.Services.AddHttpClient("douyin-direct", client =>
|
|
{
|
|
client.Timeout = TimeSpan.FromSeconds(20);
|
|
client.DefaultRequestVersion = HttpVersion.Version11;
|
|
client.DefaultVersionPolicy = HttpVersionPolicy.RequestVersionOrLower;
|
|
})
|
|
.ConfigurePrimaryHttpMessageHandler(() => CreateDouyinHttpHandler(useProxy: false));
|
|
|
|
builder.Services.AddHttpClient("bilibili", client =>
|
|
{
|
|
client.Timeout = TimeSpan.FromSeconds(20);
|
|
client.DefaultRequestVersion = HttpVersion.Version11;
|
|
client.DefaultVersionPolicy = HttpVersionPolicy.RequestVersionOrLower;
|
|
})
|
|
.ConfigurePrimaryHttpMessageHandler(static () => new SocketsHttpHandler
|
|
{
|
|
AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate | DecompressionMethods.Brotli,
|
|
PooledConnectionLifetime = TimeSpan.FromMinutes(5),
|
|
PooledConnectionIdleTimeout = TimeSpan.FromSeconds(30),
|
|
MaxConnectionsPerServer = 8,
|
|
ConnectTimeout = TimeSpan.FromSeconds(10),
|
|
UseCookies = false,
|
|
SslOptions = new SslClientAuthenticationOptions
|
|
{
|
|
EnabledSslProtocols = SslProtocols.Tls12 | SslProtocols.Tls13
|
|
}
|
|
});
|
|
|
|
var sqliteConnectionStringBuilder = new SqliteConnectionStringBuilder(
|
|
builder.Configuration.GetConnectionString("DefaultConnection"))
|
|
{
|
|
Cache = SqliteCacheMode.Shared,
|
|
Mode = SqliteOpenMode.ReadWriteCreate,
|
|
DefaultTimeout = 30
|
|
};
|
|
|
|
builder.Services.AddDbContext<LiveRecorderDbContext>(options =>
|
|
options.UseSqlite(sqliteConnectionStringBuilder.ToString()));
|
|
|
|
builder.Services.AddScoped<IUnitOfWork>(provider => provider.GetRequiredService<LiveRecorderDbContext>());
|
|
builder.Services.AddScoped<IAppSettingRepository, AppSettingRepository>();
|
|
builder.Services.AddScoped<ILiveRoomRepository, LiveRoomRepository>();
|
|
builder.Services.AddScoped<IRecordSessionRepository, RecordSessionRepository>();
|
|
builder.Services.AddScoped<IRecordTaskRepository, RecordTaskRepository>();
|
|
builder.Services.AddScoped<IRecordResultRepository, RecordResultRepository>();
|
|
builder.Services.AddScoped<ISystemLogRepository, SystemLogRepository>();
|
|
builder.Services.AddScoped<IUserAccountRepository, UserAccountRepository>();
|
|
builder.Services.AddScoped<IUserSessionRepository, UserSessionRepository>();
|
|
|
|
builder.Services.AddScoped<ISystemSettingsService, SystemSettingsService>();
|
|
builder.Services.AddScoped<ISystemLogService, SystemLogService>();
|
|
builder.Services.AddScoped<IEmailNotificationService, EmailNotificationService>();
|
|
builder.Services.AddScoped<IEventScriptService, EventScriptService>();
|
|
builder.Services.AddScoped<IAuthService, AuthService>();
|
|
builder.Services.AddScoped<LiveRoomService>();
|
|
builder.Services.AddScoped<LiveRoomStatusService>();
|
|
builder.Services.AddScoped<LiveRoomRecordingSettingsResolver>();
|
|
builder.Services.AddScoped<RecordService>();
|
|
builder.Services.AddScoped<RecordSessionService>();
|
|
builder.Services.AddScoped<StoppedOrphanRecordSessionCleanupService>();
|
|
builder.Services.AddScoped<DatabaseInitializer>();
|
|
|
|
builder.Services.AddSingleton<BilibiliWbiSigner>();
|
|
builder.Services.AddScoped<BilibiliHttpClient>();
|
|
builder.Services.AddScoped<DouyinHttpClient>();
|
|
builder.Services.AddSingleton<DouyinXBogusSigner>();
|
|
builder.Services.AddSingleton<DouyinLiveWsSignatureSigner>();
|
|
builder.Services.AddScoped<ILivePlatformAdapter, DouyinLivePlatformAdapter>();
|
|
builder.Services.AddScoped<ILivePlatformAdapter, BilibiliLivePlatformAdapter>();
|
|
builder.Services.AddScoped<ILivePlatformAdapter, HuyaLivePlatformAdapter>();
|
|
builder.Services.AddScoped<ILivePlatformAdapterFactory, LivePlatformAdapterFactory>();
|
|
builder.Services.AddScoped<ILiveDanmakuAdapter, DouyinDanmakuAdapter>();
|
|
builder.Services.AddScoped<ILiveDanmakuAdapter, BilibiliDanmakuAdapter>();
|
|
builder.Services.AddScoped<ILiveDanmakuAdapterFactory, LiveDanmakuAdapterFactory>();
|
|
|
|
builder.Services.AddSingleton<IFfmpegService, FfmpegService>();
|
|
builder.Services.AddSingleton<IStorageGuardService, StorageGuardService>();
|
|
builder.Services.AddScoped<IRecordMediaService, RecordMediaService>();
|
|
builder.Services.AddHostedService<LiveRoomPollingBackgroundService>();
|
|
|
|
var app = builder.Build();
|
|
|
|
app.UseMiddleware<ExceptionHandlingMiddleware>();
|
|
app.UseSwagger();
|
|
app.UseSwaggerUI();
|
|
app.UseCors("frontend");
|
|
app.UseMiddleware<ApiTokenAuthenticationMiddleware>();
|
|
|
|
app.MapGet("/", () => Results.Redirect("/swagger"));
|
|
app.MapControllers();
|
|
|
|
if (!resetRecordingData)
|
|
{
|
|
using var scope = app.Services.CreateScope();
|
|
var initializer = scope.ServiceProvider.GetRequiredService<DatabaseInitializer>();
|
|
await initializer.InitializeAsync();
|
|
}
|
|
|
|
if (resetRecordingData)
|
|
{
|
|
using var scope = app.Services.CreateScope();
|
|
var dbContext = scope.ServiceProvider.GetRequiredService<LiveRecorderDbContext>();
|
|
var before = await ReadRecordingDataCountsAsync(dbContext);
|
|
|
|
await using (var transaction = await dbContext.Database.BeginTransactionAsync())
|
|
{
|
|
await dbContext.Database.ExecuteSqlRawAsync(
|
|
"DELETE FROM SystemLogEntries WHERE LiveRoomId IS NOT NULL OR RecordSessionId IS NOT NULL OR RecordTaskId IS NOT NULL;");
|
|
await dbContext.Database.ExecuteSqlRawAsync("DELETE FROM RecordResults;");
|
|
await dbContext.Database.ExecuteSqlRawAsync("DELETE FROM RecordTasks;");
|
|
await dbContext.Database.ExecuteSqlRawAsync("DELETE FROM RecordSessions;");
|
|
await dbContext.Database.ExecuteSqlRawAsync("DELETE FROM LiveRooms;");
|
|
await transaction.CommitAsync();
|
|
}
|
|
|
|
var after = await ReadRecordingDataCountsAsync(dbContext);
|
|
Console.WriteLine("Recording data reset complete.");
|
|
foreach (var key in before.Keys)
|
|
{
|
|
Console.WriteLine($"{key}: {before[key]} -> {after[key]}");
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
app.Run();
|
|
|
|
static async Task<Dictionary<string, long>> ReadRecordingDataCountsAsync(LiveRecorderDbContext dbContext)
|
|
{
|
|
return new Dictionary<string, long>(StringComparer.OrdinalIgnoreCase)
|
|
{
|
|
["LiveRooms"] = await dbContext.LiveRooms.LongCountAsync(),
|
|
["RecordSessions"] = await dbContext.RecordSessions.LongCountAsync(),
|
|
["RecordTasks"] = await dbContext.RecordTasks.LongCountAsync(),
|
|
["RecordResults"] = await dbContext.RecordResults.LongCountAsync(),
|
|
["SystemLogEntries(Related)"] = await dbContext.SystemLogEntries.LongCountAsync(
|
|
item => item.LiveRoomId != null || item.RecordSessionId != null || item.RecordTaskId != null)
|
|
};
|
|
}
|
|
|
|
static SocketsHttpHandler CreateDouyinHttpHandler(bool useProxy)
|
|
{
|
|
return new SocketsHttpHandler
|
|
{
|
|
AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate | DecompressionMethods.Brotli,
|
|
PooledConnectionLifetime = TimeSpan.FromMinutes(2),
|
|
PooledConnectionIdleTimeout = TimeSpan.FromSeconds(30),
|
|
MaxConnectionsPerServer = 8,
|
|
ConnectTimeout = TimeSpan.FromSeconds(10),
|
|
UseCookies = false,
|
|
UseProxy = useProxy,
|
|
SslOptions = new SslClientAuthenticationOptions
|
|
{
|
|
EnabledSslProtocols = SslProtocols.Tls12 | SslProtocols.Tls13
|
|
}
|
|
};
|
|
}
|