2c9016a383
The 3.9.1 hotfix only repaired WizardDraftRepository, the most common
Dapper call in the wizard. The same AOT-unsafe CommandDefinition pattern
remained in 4 other places that the user hit immediately after the
deploy: the 'Choose visibility' wizard step triggers GetOwnerClubsAsync
when the user picks 'Публичная в витрине клуба' or 'Только для членов
клуба'. The wizard swallowed PlatformNotSupportedException, the
callback ack replied with '⚠️ Ошибка', and the next step never rendered.
Privacy 'didn't stick' from the user's perspective.
Two changes to fix the Discord side as well:
1. Switched GetOwnerClubsAsync / LoadClubsAsync / LoadManagerUserIdsAsync
to the direct (sql, params) overload across TelegramWizardMessenger,
DiscordWizardMessenger, DiscordWizardInteractionModule, and
DiscordPermissionLookup — same pattern as the 3.9.1 fix.
2. Added Dapper.AOT module attribute ([module: Dapper.DapperAot]) and
InterceptorsPreviewNamespaces to the DiscordBot project. The
DiscordBot assembly was previously skipped by the AOT source
generator, so even the direct-overload fix wouldn't have produced
interceptors for the Discord-specific Dapper call sites. With this
addition, the generator emits 3 DiscordBot-specific interceptors
(DiscordWizardMessenger, DiscordWizardInteractionModule,
DiscordPermissionLookup) and the AssemblyLoad ships with the right
GmRelay.DiscordBot.generated.cs.
Also expanded the AOT shape regression tests to cover all 4
CommandDefinition sites + added a 'containingClass' parameter to
ExtractMethodBody to disambiguate the duplicated LoadClubsAsync names
in DiscordWizardInteractionModule.
Bumps: 3.9.1 -> 3.9.2.
124 lines
6.1 KiB
C#
124 lines
6.1 KiB
C#
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<DiscordOptions>() ?? new DiscordOptions();
|
|
discordOptions.Validate();
|
|
|
|
builder.Services.AddSingleton(discordOptions);
|
|
|
|
builder.Logging.AddConsole();
|
|
|
|
builder.Services.AddSingleton<NpgsqlDataSource>(sp =>
|
|
{
|
|
var config = sp.GetRequiredService<IConfiguration>();
|
|
var loggerFactory = sp.GetRequiredService<ILoggerFactory>();
|
|
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<DiscordPermissionChecker>();
|
|
builder.Services.AddSingleton<DiscordListSessionsHandler>();
|
|
builder.Services.AddSingleton<DiscordDeleteSessionHandler>();
|
|
builder.Services.AddSingleton<DiscordNewSessionHandler>();
|
|
builder.Services.AddSingleton<DiscordRescheduleHandler>();
|
|
builder.Services.AddSingleton<GmRelay.Shared.Features.Sessions.RescheduleSession.HandleRescheduleVoteHandler>();
|
|
builder.Services.AddSingleton<DiscordRescheduleVoteHandler>();
|
|
builder.Services.AddSingleton<IScheduleMessageUpdateLock, ScheduleMessageUpdateLock>();
|
|
builder.Services.AddSingleton<JoinSessionHandler>();
|
|
builder.Services.AddSingleton<LeaveSessionHandler>();
|
|
builder.Services.AddSingleton<DiscordInteractionReplyCache>();
|
|
builder.Services.AddSingleton<IPlatformMessenger, DiscordPlatformMessenger>();
|
|
builder.Services.AddSingleton<ISystemClock, SystemClock>();
|
|
builder.Services.AddSingleton(new PlatformSchedulerOptions(PlatformKind.Discord));
|
|
builder.Services.AddSingleton<ISessionTriggerStore, DbSessionTriggerStore>();
|
|
builder.Services.AddSingleton<PlatformDirectNotificationSender>();
|
|
builder.Services.AddSingleton<SendConfirmationHandler>();
|
|
builder.Services.AddSingleton<ISendConfirmationHandler>(sp => sp.GetRequiredService<SendConfirmationHandler>());
|
|
builder.Services.AddSingleton<SendJoinLinkHandler>();
|
|
builder.Services.AddSingleton<ISendJoinLinkHandler>(sp => sp.GetRequiredService<SendJoinLinkHandler>());
|
|
builder.Services.AddSingleton<SendOneHourReminderHandler>();
|
|
builder.Services.AddSingleton<ISendOneHourReminderHandler>(sp => sp.GetRequiredService<SendOneHourReminderHandler>());
|
|
builder.Services.AddSingleton<HandleRsvpHandler>();
|
|
builder.Services.AddSingleton<RescheduleVotingFinalizer>();
|
|
builder.Services.AddHostedService<SessionSchedulerService>();
|
|
builder.Services.AddHostedService<DiscordRescheduleVotingDeadlineService>();
|
|
builder.Services.AddHostedService<DiscordHealthCheckHostedService>();
|
|
|
|
// ── 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<IWizardDraftRepository, GmRelay.Shared.Features.Sessions.CreateSession.Wizard.WizardDraftRepository>();
|
|
builder.Services.AddSingleton<IWizardContextStore, DiscordWizardContextStore>();
|
|
builder.Services.AddSingleton<IWizardMessenger, DiscordWizardMessenger>();
|
|
builder.Services.AddSingleton<GmRelay.Shared.Features.Sessions.CreateSession.Wizard.GameCreationWizard>();
|
|
builder.Services.AddSingleton<DiscordWizardSubmitter>();
|
|
builder.Services.AddSingleton<WizardInteractionDispatcher>();
|
|
builder.Services.AddSingleton<DiscordWizardButtonModule>();
|
|
builder.Services.AddSingleton<DiscordWizardStringMenuModule>();
|
|
builder.Services.AddSingleton<DiscordWizardModalModule>();
|
|
|
|
builder.Services
|
|
.AddDiscordGateway(options =>
|
|
{
|
|
options.Token = discordOptions.Token;
|
|
options.Intents = GatewayIntents.Guilds;
|
|
})
|
|
.AddApplicationCommands<SlashCommandInteraction, SlashCommandContext>()
|
|
.AddComponentInteractions<ButtonInteraction, ButtonInteractionContext>()
|
|
.AddComponentInteractions<StringMenuInteraction, StringMenuInteractionContext>()
|
|
.AddComponentInteractions<ModalInteraction, ModalInteractionContext>()
|
|
.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();
|