using GmRelay.DiscordBot; using GmRelay.DiscordBot.Features.Sessions; using GmRelay.DiscordBot.Features.Sessions.Wizard; using GmRelay.DiscordBot.Infrastructure; using GmRelay.DiscordBot.Infrastructure.Discord; using GmRelay.DiscordBot.Infrastructure.Health; using GmRelay.DiscordBot.Infrastructure.Logging; using GmRelay.Shared.Features.Confirmation.HandleRsvp; using GmRelay.Shared.Features.Confirmation.SendConfirmation; using GmRelay.Shared.Features.Notifications; using GmRelay.Shared.Features.Reminders.SendJoinLink; using GmRelay.Shared.Features.Reminders.SendOneHourReminder; using GmRelay.Shared.Features.Sessions.CreateSession; using GmRelay.Shared.Features.Sessions.CreateSession.Wizard; using GmRelay.Shared.Features.Sessions.RescheduleSession; using GmRelay.Shared.Infrastructure.Scheduling; using GmRelay.Shared.Platform; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using NetCord; using NetCord.Gateway; using NetCord.Hosting.Gateway; using NetCord.Hosting.Services; using NetCord.Hosting.Services.ApplicationCommands; using NetCord.Hosting.Services.ComponentInteractions; using NetCord.Services.ApplicationCommands; using NetCord.Services.ComponentInteractions; using Npgsql; [module: Dapper.DapperAot] var builder = Host.CreateApplicationBuilder(args); builder.AddServiceDefaults(); var discordOptions = builder.Configuration .GetRequiredSection("Discord") .Get() ?? new DiscordOptions(); discordOptions.Validate(); builder.Services.AddSingleton(discordOptions); builder.Logging.AddConsole(); builder.Services.AddSingleton(sp => { var config = sp.GetRequiredService(); var loggerFactory = sp.GetRequiredService(); var connectionString = config.GetConnectionString("gmrelaydb") ?? throw new InvalidOperationException( "ConnectionStrings:gmrelaydb is required. Set via environment variable ConnectionStrings__gmrelaydb."); var logger = loggerFactory.CreateLogger("GmRelay.DiscordBot.Startup"); logger.LogInformation( "Configured PostgreSQL data source with connection string {ConnectionString}", SecretRedactor.RedactConnectionString(connectionString)); return NpgsqlDataSource.Create(connectionString); }); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(new PlatformSchedulerOptions(PlatformKind.Discord)); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(sp => sp.GetRequiredService()); builder.Services.AddSingleton(); builder.Services.AddSingleton(sp => sp.GetRequiredService()); builder.Services.AddSingleton(); builder.Services.AddSingleton(sp => sp.GetRequiredService()); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddHostedService(); builder.Services.AddHostedService(); builder.Services.AddHostedService(); // ── Wizard services (issue #112) ────────────────────────────────────── // The Discord wizard reuses the platform-neutral state machine in // GmRelay.Shared (GameCreationWizard, IWizardMessenger, // IWizardDraftRepository) and only adds a Discord-specific messenger, // step renderer, slash command, and submitter on top. The wizard's // cleanup service is shared with the Telegram bot and is not // registered here — it would compete on the same drafts table. builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services .AddDiscordGateway(options => { options.Token = discordOptions.Token; options.Intents = GatewayIntents.Guilds; }) .AddApplicationCommands() .AddComponentInteractions() .AddComponentInteractions() .AddComponentInteractions() .AddGatewayHandlers(typeof(Program).Assembly); var host = builder.Build(); host.AddSlashCommand("ping", "Checks whether GM-Relay Discord is online.", () => "Pong!"); host.AddModules(typeof(Program).Assembly); await host.RunAsync();