feat(discord): add /listsessions slash command and handler
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
using NetCord.Rest;
|
||||
using NetCord.Services.ApplicationCommands;
|
||||
|
||||
namespace GmRelay.DiscordBot.Features.Sessions;
|
||||
|
||||
[SlashCommand("listsessions", "Show upcoming game sessions in this server")]
|
||||
public class DiscordListSessionsCommand : ApplicationCommandModule<SlashCommandContext>
|
||||
{
|
||||
private readonly DiscordListSessionsHandler _handler;
|
||||
|
||||
public DiscordListSessionsCommand(DiscordListSessionsHandler handler)
|
||||
{
|
||||
_handler = handler;
|
||||
}
|
||||
|
||||
public async Task ExecuteAsync()
|
||||
{
|
||||
var guildId = Context.Guild?.Id.ToString()
|
||||
?? throw new InvalidOperationException("This command can only be used in a guild.");
|
||||
var channelId = Context.Channel.Id.ToString();
|
||||
|
||||
var view = await _handler.BuildScheduleAsync(guildId, channelId, CancellationToken.None);
|
||||
|
||||
if (view is null)
|
||||
{
|
||||
await Context.Interaction.SendResponseAsync(
|
||||
InteractionCallback.Message("📭 В этом сервере нет предстоящих игр."));
|
||||
return;
|
||||
}
|
||||
|
||||
var (embeds, actionRows) = Rendering.DiscordSessionBatchRenderer.Render(view);
|
||||
|
||||
await Context.Interaction.SendResponseAsync(
|
||||
InteractionCallback.Message(new InteractionMessageProperties()
|
||||
.WithEmbeds(embeds)
|
||||
.WithComponents(actionRows)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
using Dapper;
|
||||
using GmRelay.Shared.Domain;
|
||||
using GmRelay.Shared.Rendering;
|
||||
using Npgsql;
|
||||
|
||||
namespace GmRelay.DiscordBot.Features.Sessions;
|
||||
|
||||
internal sealed record DiscordSessionListItemDto(
|
||||
Guid Id, string Title, DateTime ScheduledAt, string Status, int? MaxPlayers,
|
||||
int PlayerCount, int WaitlistCount);
|
||||
|
||||
public sealed class DiscordListSessionsHandler(NpgsqlDataSource dataSource)
|
||||
{
|
||||
public async Task<SessionBatchViewModel?> BuildScheduleAsync(
|
||||
string guildId,
|
||||
string channelId,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
await using var connection = await dataSource.OpenConnectionAsync(cancellationToken);
|
||||
|
||||
var sessions = await connection.QueryAsync<DiscordSessionListItemDto>(
|
||||
@"SELECT s.id as Id, s.title as Title, s.scheduled_at as ScheduledAt, s.status as Status,
|
||||
s.max_players as MaxPlayers,
|
||||
COUNT(sp.id) FILTER (WHERE sp.is_gm = false AND sp.registration_status = @Active) as PlayerCount,
|
||||
COUNT(sp.id) FILTER (WHERE sp.is_gm = false AND sp.registration_status = @Waitlisted) as WaitlistCount
|
||||
FROM sessions s
|
||||
JOIN game_groups g ON s.group_id = g.id
|
||||
LEFT JOIN session_participants sp ON s.id = sp.session_id
|
||||
WHERE g.platform = 'Discord'
|
||||
AND g.external_group_id = @GuildId
|
||||
AND s.status != @Cancelled
|
||||
AND s.scheduled_at > NOW()
|
||||
GROUP BY s.id, s.title, s.scheduled_at, s.status, s.max_players
|
||||
ORDER BY s.scheduled_at ASC",
|
||||
new
|
||||
{
|
||||
GuildId = guildId,
|
||||
Cancelled = SessionStatus.Cancelled,
|
||||
Active = ParticipantRegistrationStatus.Active,
|
||||
Waitlisted = ParticipantRegistrationStatus.Waitlisted
|
||||
});
|
||||
|
||||
var sessionList = sessions.ToList();
|
||||
if (sessionList.Count == 0)
|
||||
return null;
|
||||
|
||||
var sessionIds = sessionList.Select(s => s.Id).ToList();
|
||||
var participants = await connection.QueryAsync<ParticipantBatchDto>(
|
||||
@"SELECT sp.session_id as SessionId,
|
||||
p.display_name as DisplayName,
|
||||
COALESCE(p.external_username, p.telegram_username) as TelegramUsername,
|
||||
sp.registration_status as RegistrationStatus
|
||||
FROM session_participants sp
|
||||
JOIN players p ON p.id = sp.player_id
|
||||
WHERE sp.session_id = ANY(@SessionIds) AND sp.is_gm = false
|
||||
ORDER BY sp.registration_status ASC, sp.created_at ASC",
|
||||
new { SessionIds = sessionIds });
|
||||
|
||||
var firstTitle = sessionList.First().Title;
|
||||
var batchDtos = sessionList.Select(s => new SessionBatchDto(
|
||||
s.Id, s.ScheduledAt, s.Status, s.MaxPlayers, "")).ToList();
|
||||
|
||||
return SessionBatchViewBuilder.Build(firstTitle, batchDtos, participants.ToList());
|
||||
}
|
||||
}
|
||||
@@ -10,9 +10,11 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Aspire.Npgsql" Version="13.2.2" />
|
||||
<PackageReference Include="Dapper" Version="2.1.72" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.5" />
|
||||
<PackageReference Include="NetCord.Hosting" Version="1.0.0-alpha.489" />
|
||||
<PackageReference Include="NetCord.Hosting.Services" Version="1.0.0-alpha.489" />
|
||||
<PackageReference Include="NetCord.Services" Version="1.0.0-alpha.489" />
|
||||
<PackageReference Include="Npgsql" Version="10.0.2" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user