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() ?? ["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() } }); }); 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(options => options.UseSqlite(sqliteConnectionStringBuilder.ToString())); builder.Services.AddScoped(provider => provider.GetRequiredService()); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddSingleton(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddScoped(); builder.Services.AddHostedService(); var app = builder.Build(); app.UseMiddleware(); app.UseSwagger(); app.UseSwaggerUI(); app.UseCors("frontend"); app.UseMiddleware(); app.MapGet("/", () => Results.Redirect("/swagger")); app.MapControllers(); if (!resetRecordingData) { using var scope = app.Services.CreateScope(); var initializer = scope.ServiceProvider.GetRequiredService(); await initializer.InitializeAsync(); } if (resetRecordingData) { using var scope = app.Services.CreateScope(); var dbContext = scope.ServiceProvider.GetRequiredService(); 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> ReadRecordingDataCountsAsync(LiveRecorderDbContext dbContext) { return new Dictionary(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 } }; }