[Activity] Add thread add and remove player commands

This commit is contained in:
2023-03-24 00:01:04 +01:00
parent bd52e37454
commit a3051ed060
6 changed files with 166 additions and 61 deletions

View File

@@ -24,6 +24,8 @@ public partial class ActivityModule : InteractionModuleBase<SocketInteractionCon
private readonly ActivitiesRepository _activitiesRepository; private readonly ActivitiesRepository _activitiesRepository;
private readonly ActivityFormatter _activityFormatter; private readonly ActivityFormatter _activityFormatter;
private SocketGuildUser User => (SocketGuildUser) Context.User;
public ActivityModule(ILogger<ActivityModule> logger, IOptions<ActivityOptions> options, ActivityHelper activityHelper, ActivitiesRepository activitiesRepository, ActivityFormatter activityFormatter) public ActivityModule(ILogger<ActivityModule> logger, IOptions<ActivityOptions> options, ActivityHelper activityHelper, ActivitiesRepository activitiesRepository, ActivityFormatter activityFormatter)
{ {
_logger = logger; _logger = logger;
@@ -155,7 +157,8 @@ public partial class ActivityModule : InteractionModuleBase<SocketInteractionCon
{ {
activity = new StagedActivity activity = new StagedActivity
{ {
ActivityId = response.Id, MessageId = response.Id,
ChannelId = Context.Channel.Id,
GuildId = Context.Guild.Id, GuildId = Context.Guild.Id,
ThreadId = threadId, ThreadId = threadId,
CreatorUserId = Context.User.Id, CreatorUserId = Context.User.Id,
@@ -172,7 +175,8 @@ public partial class ActivityModule : InteractionModuleBase<SocketInteractionCon
{ {
activity = new InterstellarActivity activity = new InterstellarActivity
{ {
ActivityId = response.Id, MessageId = response.Id,
ChannelId = Context.Channel.Id,
GuildId = Context.Guild.Id, GuildId = Context.Guild.Id,
ThreadId = threadId, ThreadId = threadId,
CreatorUserId = Context.User.Id, CreatorUserId = Context.User.Id,
@@ -189,7 +193,8 @@ public partial class ActivityModule : InteractionModuleBase<SocketInteractionCon
{ {
activity = new Activity activity = new Activity
{ {
ActivityId = response.Id, MessageId = response.Id,
ChannelId = Context.Channel.Id,
GuildId = Context.Guild.Id, GuildId = Context.Guild.Id,
ThreadId = threadId, ThreadId = threadId,
CreatorUserId = Context.User.Id, CreatorUserId = Context.User.Id,
@@ -206,7 +211,26 @@ public partial class ActivityModule : InteractionModuleBase<SocketInteractionCon
await _activitiesRepository.AddActivity(activity); await _activitiesRepository.AddActivity(activity);
// Add creator to activity // Add creator to activity
var rolePlayer = areRolesEnabled ? new ActivityRolePlayer var rolePlayer = CreateActivityPlayer(activity, user, areRolesEnabled);
activity.ActivityPlayers.Add(rolePlayer);
await _activitiesRepository.SaveChanges();
// Add components
var components = ActivityComponents(activity.MessageId);
await ModifyOriginalResponseAsync(m =>
{
m.Content = "";
m.Components = components.Build();
m.Embed = _activityFormatter.ActivityEmbed(activity, Enumerable.Repeat(rolePlayer, 1).ToImmutableList()).Build();
});
}
private ActivityPlayer CreateActivityPlayer(Activity activity, SocketGuildUser user, bool areRolesEnabled)
{
return areRolesEnabled ? new ActivityRolePlayer
{ {
Activity = activity, Activity = activity,
UserId = user.Id, UserId = user.Id,
@@ -218,29 +242,15 @@ public partial class ActivityModule : InteractionModuleBase<SocketInteractionCon
UserId = user.Id, UserId = user.Id,
Name = user.DisplayName Name = user.DisplayName
}; };
activity.ActivityPlayers.Add(rolePlayer);
await _activitiesRepository.SaveChanges();
// Add components
var components = ActivityComponents(activity.ActivityId);
await ModifyOriginalResponseAsync(m =>
{
m.Content = "";
m.Components = components.Build();
m.Embed = _activityFormatter.ActivityEmbed(activity, Enumerable.Repeat(rolePlayer, 1).ToImmutableList()).Build();
});
} }
[ComponentInteraction("activity join:*", ignoreGroupNames: true)] [ComponentInteraction("activity join:*", ignoreGroupNames: true)]
public async Task JoinActivity(ulong activityId) public async Task JoinActivity(ulong messageId)
{ {
var user = (SocketGuildUser)Context.User; var user = (SocketGuildUser)Context.User;
// Check if activity exists // Check if activity exists
if (await _activitiesRepository.FindActivity(Context.Guild.Id, activityId) is not { } activity) if (await _activitiesRepository.FindActivity(Context.Guild.Id, Context.Channel.Id, messageId) is not { } activity)
{ {
await RespondAsync( await RespondAsync(
ephemeral: true, ephemeral: true,
@@ -251,7 +261,7 @@ public partial class ActivityModule : InteractionModuleBase<SocketInteractionCon
} }
// If player is already registered // If player is already registered
if (await _activitiesRepository.FindActivityPlayer( Context.Guild.Id, activityId, user.Id) is not null) if (await _activitiesRepository.FindActivityPlayer( Context.Guild.Id, Context.Channel.Id, messageId, user.Id) is not null)
{ {
await RespondAsync( await RespondAsync(
ephemeral: true, ephemeral: true,
@@ -262,7 +272,7 @@ public partial class ActivityModule : InteractionModuleBase<SocketInteractionCon
} }
// Check if activity is full // Check if activity is full
if (await _activitiesRepository.ActivityPlayerCount(activity) >= activity.MaxPlayers) if (_activitiesRepository.ActivityPlayerCount(activity) >= activity.MaxPlayers)
{ {
await RespondAsync( await RespondAsync(
ephemeral: true, ephemeral: true,
@@ -272,20 +282,9 @@ public partial class ActivityModule : InteractionModuleBase<SocketInteractionCon
return; return;
} }
_logger.LogTrace("Player {Player} joined activity {Id}", user.DisplayName, activityId); _logger.LogTrace("Player {Player} joined activity {Id}", user.DisplayName, messageId);
var activityPlayer = activity.AreRolesEnabled ? new ActivityRolePlayer var activityPlayer = CreateActivityPlayer(activity, User, activity.AreRolesEnabled);
{
Activity = activity,
UserId = user.Id,
Name = user.DisplayName,
Roles = _activityHelper.GetPlayerRoles(user.Roles)
} : new ActivityPlayer
{
Activity = activity,
UserId = user.Id,
Name = user.DisplayName
};
// Add player to activity // Add player to activity
activity.ActivityPlayers.Add(activityPlayer); activity.ActivityPlayers.Add(activityPlayer);
@@ -301,12 +300,12 @@ public partial class ActivityModule : InteractionModuleBase<SocketInteractionCon
} }
[ComponentInteraction("activity leave:*", ignoreGroupNames: true)] [ComponentInteraction("activity leave:*", ignoreGroupNames: true)]
public async Task LeaveActivity(ulong activityId) public async Task LeaveActivity(ulong messageId)
{ {
var user = (IGuildUser)Context.User; var user = (IGuildUser)Context.User;
// Check if activity exists // Check if activity exists
if (await _activitiesRepository.FindActivity(Context.Guild.Id, activityId) is not { } activity) if (await _activitiesRepository.FindActivity(Context.Guild.Id, Context.Channel.Id, messageId) is not { } activity)
{ {
await RespondAsync( await RespondAsync(
ephemeral: true, ephemeral: true,
@@ -317,7 +316,7 @@ public partial class ActivityModule : InteractionModuleBase<SocketInteractionCon
} }
// Check if player is in activity // Check if player is in activity
if (await _activitiesRepository.FindActivityPlayer(Context.Guild.Id, activityId, user.Id) is not { } activityPlayer) if (await _activitiesRepository.FindActivityPlayer(Context.Guild.Id, Context.Channel.Id, messageId, user.Id) is not { } activityPlayer)
{ {
await RespondAsync( await RespondAsync(
ephemeral: true, ephemeral: true,
@@ -327,7 +326,7 @@ public partial class ActivityModule : InteractionModuleBase<SocketInteractionCon
return; return;
} }
_logger.LogTrace("Player {Player} left activity {Id}", user.DisplayName, activityId); _logger.LogTrace("Player {Player} left activity {Id}", user.DisplayName, messageId);
activity.ActivityPlayers.Remove(activityPlayer); activity.ActivityPlayers.Remove(activityPlayer);
await _activitiesRepository.SaveChanges(); await _activitiesRepository.SaveChanges();
@@ -342,12 +341,12 @@ public partial class ActivityModule : InteractionModuleBase<SocketInteractionCon
} }
[ComponentInteraction("activity delete:*", ignoreGroupNames: true)] [ComponentInteraction("activity delete:*", ignoreGroupNames: true)]
public async Task DeleteActivity(ulong activityId) public async Task DeleteActivity(ulong messageId)
{ {
var user = (SocketGuildUser) Context.User; var user = (SocketGuildUser) Context.User;
// Check if activity exists // Check if activity exists
if (await _activitiesRepository.FindActivity(Context.Guild.Id, activityId) is not { } activity) if (await _activitiesRepository.FindActivity(Context.Guild.Id, Context.Channel.Id, messageId) is not { } activity)
{ {
await RespondAsync( await RespondAsync(
ephemeral: true, ephemeral: true,
@@ -376,15 +375,13 @@ public partial class ActivityModule : InteractionModuleBase<SocketInteractionCon
await Context.Guild.GetThreadChannel(activity.ThreadId).DeleteAsync(); await Context.Guild.GetThreadChannel(activity.ThreadId).DeleteAsync();
// Delete response // Delete response
await Context.Channel.DeleteMessageAsync(activityId); await Context.Channel.DeleteMessageAsync(messageId);
} }
private async Task UpdateActivityEmbed(Activity activity, ActivityUpdateReason updateReason) private async Task UpdateActivityEmbed(Activity activity, ActivityUpdateReason updateReason)
{ {
// Get channel // Get channel
var channel = await Context.Interaction.GetChannelAsync(); if (Context.Guild.GetChannel(activity.ChannelId) is not SocketTextChannel channel)
if (channel is null)
{ {
return; return;
} }
@@ -392,7 +389,7 @@ public partial class ActivityModule : InteractionModuleBase<SocketInteractionCon
// Fetch players // Fetch players
var players = await _activitiesRepository.LoadActivityPlayers(activity); var players = await _activitiesRepository.LoadActivityPlayers(activity);
await channel.ModifyMessageAsync(activity.ActivityId, properties => await channel.ModifyMessageAsync(activity.MessageId, properties =>
{ {
properties.Embed = _activityFormatter.ActivityEmbed(activity, players).Build(); properties.Embed = _activityFormatter.ActivityEmbed(activity, players).Build();
@@ -400,8 +397,8 @@ public partial class ActivityModule : InteractionModuleBase<SocketInteractionCon
var isActivityFull = players.Count >= activity.MaxPlayers; var isActivityFull = players.Count >= activity.MaxPlayers;
properties.Components = updateReason switch properties.Components = updateReason switch
{ {
ActivityUpdateReason.PlayerJoin when isActivityFull => ActivityComponents(activity.ActivityId, disabled: true).Build(), ActivityUpdateReason.PlayerJoin when isActivityFull => ActivityComponents(activity.MessageId, disabled: true).Build(),
ActivityUpdateReason.PlayerLeave when !isActivityFull => ActivityComponents(activity.ActivityId, disabled: false).Build(), ActivityUpdateReason.PlayerLeave when !isActivityFull => ActivityComponents(activity.MessageId, disabled: false).Build(),
_ => Optional<MessageComponent>.Unspecified _ => Optional<MessageComponent>.Unspecified
}; };
}); });

View File

@@ -13,7 +13,7 @@ public partial class ActivityModule
{ {
if (message is IUserMessage userMessage && userMessage.Author.IsBot) if (message is IUserMessage userMessage && userMessage.Author.IsBot)
{ {
if (await _activitiesRepository.FindActivity(Context.Guild.Id, message.Id) is { } activity) if (await _activitiesRepository.FindActivity(Context.Guild.Id, Context.Channel.Id, message.Id) is { } activity)
{ {
// Generate random player // Generate random player
var player = new ActivityRolePlayer var player = new ActivityRolePlayer

View File

@@ -1,4 +1,8 @@
using Discord; using System.Diagnostics.CodeAnalysis;
using Cocotte.Modules.Activities.Models;
using Cocotte.Utils;
using Discord;
using Discord.Interactions;
using Discord.WebSocket; using Discord.WebSocket;
namespace Cocotte.Modules.Activities; namespace Cocotte.Modules.Activities;
@@ -40,4 +44,92 @@ public partial class ActivityModule
return thread.Id; return thread.Id;
} }
[SlashCommand("ajouter", "Ajouter un joueur à cette activité")]
public async Task ThreadAddPlayer(IUser user)
{
// Get activity linked to this thread
var activity = _activitiesRepository.FindActivityByThreadId(Context.Channel.Id);
if (!await CheckCommandInThread(activity) || activity is null)
{
return;
}
// Check that the user is not already in the activity
if (await _activitiesRepository.ActivityContainsUser(activity, user.Id))
{
await RespondAsync(
ephemeral: true,
embed: EmbedUtils.ErrorEmbed("Cet utilisateur est **déjà inscrit** à cette activité").Build()
);
return;
}
var activityPlayer = CreateActivityPlayer(activity, (SocketGuildUser) user, activity.AreRolesEnabled);
activity.ActivityPlayers.Add(activityPlayer);
await _activitiesRepository.SaveChanges();
await UpdateActivityEmbed(activity, ActivityUpdateReason.PlayerJoin);
await RespondAsync(
ephemeral: true,
embed: EmbedUtils.SuccessEmbed($"{((IGuildUser) user).DisplayName} a bien été **ajouté** à l'activité").Build()
);
}
[SlashCommand("ajouter", "Ajouter un joueur à cette activité")]
public async Task ThreadRemovePlayer(IUser user)
{
// TODO: Autocomplete
// Get activity linked to this thread
var activity = _activitiesRepository.FindActivityByThreadId(Context.Channel.Id);
if (!await CheckCommandInThread(activity) || activity is null)
{
return;
}
// Check that the user is not already in the activity
if (await _activitiesRepository.ActivityContainsUser(activity, user.Id))
{
await RespondAsync(
ephemeral: true,
embed: EmbedUtils.ErrorEmbed("Cet utilisateur est **déjà inscrit** à cette activité").Build()
);
return;
}
var activityPlayer = CreateActivityPlayer(activity, (SocketGuildUser) user, activity.AreRolesEnabled);
activity.ActivityPlayers.Add(activityPlayer);
await _activitiesRepository.SaveChanges();
await UpdateActivityEmbed(activity, ActivityUpdateReason.PlayerJoin);
await RespondAsync(
ephemeral: true,
embed: EmbedUtils.SuccessEmbed($"{((IGuildUser) user).DisplayName} a bien été **ajouté** à l'activité").Build()
);
}
private async Task<bool> CheckCommandInThread(Activity? activity)
{
// Check if activity is not null (means we are in a valid thread)
if (activity is null)
{
await RespondAsync(
ephemeral: true,
embed: EmbedUtils.ErrorEmbed("Vous devez être dans un **thread lié à une activité** pour utiliser cette commande").Build()
);
return false;
}
return true;
}
} }

View File

@@ -12,18 +12,17 @@ public class ActivitiesRepository
_cocotteDbContext = cocotteDbContext; _cocotteDbContext = cocotteDbContext;
} }
public async Task<Activity?> FindActivity(ulong guildId, ulong activityId) public async Task<Activity?> FindActivity(ulong guildId, ulong channelId, ulong messageId)
{ {
return await _cocotteDbContext.Activities.FindAsync(guildId, activityId); return await _cocotteDbContext.Activities.FindAsync(guildId, channelId, messageId);
} }
public async Task<ActivityPlayer?> FindActivityPlayer(ulong guildId, ulong activityId, ulong playerId) public async Task<ActivityPlayer?> FindActivityPlayer(ulong guildId, ulong channelId, ulong messageId, ulong userId)
{ {
return await _cocotteDbContext.ActivityPlayers.FindAsync(guildId, activityId, playerId); return await _cocotteDbContext.ActivityPlayers.FindAsync(guildId, channelId, messageId, userId);
} }
public async Task<int> ActivityPlayerCount(Activity activity) => public int ActivityPlayerCount(Activity activity) => activity.ActivityPlayers.Count;
await _cocotteDbContext.ActivityPlayers.Where(player => player.ActivityId == activity.ActivityId).CountAsync();
public async Task<IReadOnlyCollection<ActivityPlayer>> LoadActivityPlayers(Activity activity) public async Task<IReadOnlyCollection<ActivityPlayer>> LoadActivityPlayers(Activity activity)
{ {
@@ -49,4 +48,17 @@ public class ActivitiesRepository
{ {
_cocotteDbContext.Activities.Remove(activity); _cocotteDbContext.Activities.Remove(activity);
} }
public Activity? FindActivityByThreadId(ulong threadId)
{
return _cocotteDbContext.Activities.FirstOrDefault(activity => activity.ThreadId == threadId);
}
public async Task<bool> ActivityContainsUser(Activity activity, ulong userId)
{
return await _cocotteDbContext.Entry(activity)
.Collection(a => a.ActivityPlayers)
.Query()
.AnyAsync(p => p.UserId == userId);
}
} }

View File

@@ -4,13 +4,16 @@ using Microsoft.EntityFrameworkCore;
namespace Cocotte.Modules.Activities.Models; namespace Cocotte.Modules.Activities.Models;
[PrimaryKey(nameof(GuildId), nameof(ActivityId))] [PrimaryKey(nameof(GuildId), nameof(ChannelId), nameof(MessageId))]
[Index(nameof(ThreadId))]
public class Activity public class Activity
{ {
[DatabaseGenerated(DatabaseGeneratedOption.None)] [DatabaseGenerated(DatabaseGeneratedOption.None)]
public required ulong GuildId { get; init; } public required ulong GuildId { get; init; }
[DatabaseGenerated(DatabaseGeneratedOption.None)] [DatabaseGenerated(DatabaseGeneratedOption.None)]
public required ulong ActivityId { get; init; } public required ulong ChannelId { get; init; }
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public required ulong MessageId { get; init; }
public required ulong ThreadId { get; init; } public required ulong ThreadId { get; init; }
public required ulong CreatorUserId { get; init; } public required ulong CreatorUserId { get; init; }
@@ -25,6 +28,6 @@ public class Activity
public override string ToString() public override string ToString()
{ {
return $"{nameof(ActivityId)}: {ActivityId}, {nameof(CreatorUserId)}: {CreatorUserId}, {nameof(Description)}: {Description}, {nameof(Type)}: {Type}, {nameof(Name)}: {Name}, {nameof(MaxPlayers)}: {MaxPlayers}"; return $"{nameof(MessageId)}: {MessageId}, {nameof(CreatorUserId)}: {CreatorUserId}, {nameof(Description)}: {Description}, {nameof(Type)}: {Type}, {nameof(Name)}: {Name}, {nameof(MaxPlayers)}: {MaxPlayers}";
} }
} }

View File

@@ -3,7 +3,7 @@ using Microsoft.EntityFrameworkCore;
namespace Cocotte.Modules.Activities.Models; namespace Cocotte.Modules.Activities.Models;
[PrimaryKey(nameof(GuildId), nameof(ActivityId), nameof(UserId))] [PrimaryKey(nameof(GuildId), nameof(ChannelId), nameof(MessageId), nameof(UserId))]
public class ActivityPlayer public class ActivityPlayer
{ {
[DatabaseGenerated(DatabaseGeneratedOption.None)] [DatabaseGenerated(DatabaseGeneratedOption.None)]
@@ -12,7 +12,8 @@ public class ActivityPlayer
public required string Name { get; init; } public required string Name { get; init; }
public ulong GuildId { get; set; } public ulong GuildId { get; set; }
public ulong ActivityId { get; init; } public ulong ChannelId { get; set; }
public ulong MessageId { get; init; }
public required Activity Activity { get; init; } public required Activity Activity { get; init; }
public override string ToString() public override string ToString()