[Activity] Add thread creation and start message

This commit is contained in:
2023-03-23 22:49:49 +01:00
parent 79d9f16a8b
commit bd52e37454
8 changed files with 184 additions and 48 deletions

View File

@@ -18,7 +18,7 @@ public class ActivityFormatter
_options = options.Value; _options = options.Value;
} }
public static string FormatActivityName(ActivityName activityName) public string FormatActivityName(ActivityName activityName)
{ {
return activityName switch return activityName switch
{ {
@@ -38,7 +38,7 @@ public class ActivityFormatter
}; };
} }
public static string GetActivityBanner(ActivityName activityName) public string GetActivityBanner(ActivityName activityName)
{ {
return CdnUtils.GetAsset($"banner/{GetActivityCode(activityName)}.webp"); return CdnUtils.GetAsset($"banner/{GetActivityCode(activityName)}.webp");
} }
@@ -66,9 +66,15 @@ public class ActivityFormatter
$"{FormatActivityName(activity.Name)} ({players.Count}/{activity.MaxPlayers})" $"{FormatActivityName(activity.Name)} ({players.Count}/{activity.MaxPlayers})"
}; };
string description = string.IsNullOrWhiteSpace(activity.Description) var descriptionBuilder = new StringBuilder();
? $"Rejoignez l'activité de {MentionUtils.MentionUser(activity.CreatorDiscordId)}" descriptionBuilder.AppendLine(
: activity.Description; string.IsNullOrWhiteSpace(activity.Description)
? $"Rejoignez l'activité de {MentionUtils.MentionUser(activity.CreatorUserId)}"
: activity.Description
);
descriptionBuilder.AppendLine();
descriptionBuilder.Append($"**[Fil associé]({ChannelUtils.GetChannelLink(activity.GuildId, activity.ThreadId)})**");
string bannerUrl = GetActivityBanner(activity.Name); string bannerUrl = GetActivityBanner(activity.Name);
@@ -77,7 +83,7 @@ public class ActivityFormatter
var builder = new EmbedBuilder() var builder = new EmbedBuilder()
.WithColor(color) .WithColor(color)
.WithTitle(title) .WithTitle(title)
.WithDescription(description) .WithDescription(descriptionBuilder.ToString())
.WithImageUrl(bannerUrl) .WithImageUrl(bannerUrl)
.WithFields(playersField); .WithFields(playersField);
@@ -90,7 +96,7 @@ public class ActivityFormatter
return builder; return builder;
} }
private static string GetActivityCode(ActivityName activityName) => activityName switch private string GetActivityCode(ActivityName activityName) => activityName switch
{ {
ActivityName.Abyss => "VA", ActivityName.Abyss => "VA",
ActivityName.OriginsOfWar => "OOW", ActivityName.OriginsOfWar => "OOW",

View File

@@ -33,6 +33,7 @@ public partial class ActivityModule : InteractionModuleBase<SocketInteractionCon
_options = options.Value; _options = options.Value;
} }
#if DEBUG
[RequireOwner] [RequireOwner]
[SlashCommand("setup-info", "Display activity setup info")] [SlashCommand("setup-info", "Display activity setup info")]
public async Task SetupInfo() public async Task SetupInfo()
@@ -44,6 +45,9 @@ public partial class ActivityModule : InteractionModuleBase<SocketInteractionCon
- Healer: {MentionUtils.MentionRole(_options.SupportRoleId)} {_options.SupportEmote.ToEmote()} - Healer: {MentionUtils.MentionRole(_options.SupportRoleId)} {_options.SupportEmote.ToEmote()}
"""); """);
} }
#endif
#region activities
[SlashCommand("abime-néant", "Créer un groupe pour l'Abîme du Néant")] [SlashCommand("abime-néant", "Créer un groupe pour l'Abîme du Néant")]
[Alias("abime", "abyss")] [Alias("abime", "abyss")]
@@ -128,16 +132,21 @@ public partial class ActivityModule : InteractionModuleBase<SocketInteractionCon
await CreateActivity(ActivityName.MirroriaRace, description, areRolesEnabled: false); await CreateActivity(ActivityName.MirroriaRace, description, areRolesEnabled: false);
} }
#endregion
private async Task CreateActivity(ActivityName activityName, string description, bool areRolesEnabled = true, uint? maxPlayers = null, uint? stage = null, InterstellarColor? interstellarColor = null) private async Task CreateActivity(ActivityName activityName, string description, bool areRolesEnabled = true, uint? maxPlayers = null, uint? stage = null, InterstellarColor? interstellarColor = null)
{ {
var user = (SocketGuildUser)Context.User; var user = (SocketGuildUser)Context.User;
_logger.LogTrace("{User} is creating activity {Activity}", user.DisplayName, activityName); _logger.LogTrace("{User} is creating activity {Activity}", user.DisplayName, activityName);
// Activities are identified using their original message id // Activities are identified using their original message id
await RespondAsync("> *Création de l'activité en cours...*"); await RespondAsync("> ***Création de l'activité en cours...***");
var response = await GetOriginalResponseAsync(); var response = await GetOriginalResponseAsync();
// Create associated thread
var threadId = await CreateThread(activityName, user.DisplayName);
var activityType = ActivityHelper.ActivityNameToType(activityName); var activityType = ActivityHelper.ActivityNameToType(activityName);
maxPlayers ??= ActivityHelper.ActivityTypeToMaxPlayers(activityType); maxPlayers ??= ActivityHelper.ActivityTypeToMaxPlayers(activityType);
Activity activity; Activity activity;
@@ -147,8 +156,10 @@ public partial class ActivityModule : InteractionModuleBase<SocketInteractionCon
activity = new StagedActivity activity = new StagedActivity
{ {
ActivityId = response.Id, ActivityId = response.Id,
CreatorDiscordId = Context.User.Id, GuildId = Context.Guild.Id,
CreatorDiscordName = ((SocketGuildUser) Context.User).DisplayName, ThreadId = threadId,
CreatorUserId = Context.User.Id,
CreatorDisplayName = ((SocketGuildUser) Context.User).DisplayName,
Description = description, Description = description,
Type = activityType, Type = activityType,
Name = activityName, Name = activityName,
@@ -162,8 +173,10 @@ public partial class ActivityModule : InteractionModuleBase<SocketInteractionCon
activity = new InterstellarActivity activity = new InterstellarActivity
{ {
ActivityId = response.Id, ActivityId = response.Id,
CreatorDiscordId = Context.User.Id, GuildId = Context.Guild.Id,
CreatorDiscordName = ((SocketGuildUser) Context.User).DisplayName, ThreadId = threadId,
CreatorUserId = Context.User.Id,
CreatorDisplayName = ((SocketGuildUser) Context.User).DisplayName,
Description = description, Description = description,
Type = activityType, Type = activityType,
Name = activityName, Name = activityName,
@@ -177,8 +190,10 @@ public partial class ActivityModule : InteractionModuleBase<SocketInteractionCon
activity = new Activity activity = new Activity
{ {
ActivityId = response.Id, ActivityId = response.Id,
CreatorDiscordId = Context.User.Id, GuildId = Context.Guild.Id,
CreatorDiscordName = ((SocketGuildUser) Context.User).DisplayName, ThreadId = threadId,
CreatorUserId = Context.User.Id,
CreatorDisplayName = ((SocketGuildUser) Context.User).DisplayName,
Description = description, Description = description,
Type = activityType, Type = activityType,
Name = activityName, Name = activityName,
@@ -194,17 +209,18 @@ public partial class ActivityModule : InteractionModuleBase<SocketInteractionCon
var rolePlayer = areRolesEnabled ? new ActivityRolePlayer var rolePlayer = areRolesEnabled ? new ActivityRolePlayer
{ {
Activity = activity, Activity = activity,
DiscordId = user.Id, UserId = user.Id,
Name = user.DisplayName, Name = user.DisplayName,
Roles = _activityHelper.GetPlayerRoles(user.Roles) Roles = _activityHelper.GetPlayerRoles(user.Roles)
} : new ActivityPlayer } : new ActivityPlayer
{ {
Activity = activity, Activity = activity,
DiscordId = user.Id, UserId = user.Id,
Name = user.DisplayName Name = user.DisplayName
}; };
activity.ActivityPlayers.Add(rolePlayer); activity.ActivityPlayers.Add(rolePlayer);
await _activitiesRepository.SaveChanges(); await _activitiesRepository.SaveChanges();
// Add components // Add components
@@ -219,27 +235,27 @@ public partial class ActivityModule : InteractionModuleBase<SocketInteractionCon
} }
[ComponentInteraction("activity join:*", ignoreGroupNames: true)] [ComponentInteraction("activity join:*", ignoreGroupNames: true)]
private async Task JoinActivity(ulong activityId) public async Task JoinActivity(ulong activityId)
{ {
var user = (SocketGuildUser)Context.User; var user = (SocketGuildUser)Context.User;
// Check if activity exists // Check if activity exists
if (await _activitiesRepository.FindActivity(activityId) is not { } activity) if (await _activitiesRepository.FindActivity(Context.Guild.Id, activityId) is not { } activity)
{ {
await RespondAsync( await RespondAsync(
ephemeral: true, ephemeral: true,
embed: EmbedUtils.ErrorEmbed("Cette activité n'existe plus").Build() embed: EmbedUtils.ErrorEmbed("Cette activité **n'existe plus**").Build()
); );
return; return;
} }
// If player is already registered // If player is already registered
if (await _activitiesRepository.FindActivityPlayer(activityId, user.Id) is not null) if (await _activitiesRepository.FindActivityPlayer( Context.Guild.Id, activityId, user.Id) is not null)
{ {
await RespondAsync( await RespondAsync(
ephemeral: true, ephemeral: true,
embed: EmbedUtils.ErrorEmbed("Vous êtes déjà inscrit à cette activité").Build() embed: EmbedUtils.ErrorEmbed("Vous êtes **déjà inscrit** à cette activité").Build()
); );
return; return;
@@ -250,7 +266,7 @@ public partial class ActivityModule : InteractionModuleBase<SocketInteractionCon
{ {
await RespondAsync( await RespondAsync(
ephemeral: true, ephemeral: true,
embed: EmbedUtils.ErrorEmbed("L'activité est complète").Build() embed: EmbedUtils.ErrorEmbed("L'activité est **complète**").Build()
); );
return; return;
@@ -261,13 +277,13 @@ public partial class ActivityModule : InteractionModuleBase<SocketInteractionCon
var activityPlayer = activity.AreRolesEnabled ? new ActivityRolePlayer var activityPlayer = activity.AreRolesEnabled ? new ActivityRolePlayer
{ {
Activity = activity, Activity = activity,
DiscordId = user.Id, UserId = user.Id,
Name = user.DisplayName, Name = user.DisplayName,
Roles = _activityHelper.GetPlayerRoles(user.Roles) Roles = _activityHelper.GetPlayerRoles(user.Roles)
} : new ActivityPlayer } : new ActivityPlayer
{ {
Activity = activity, Activity = activity,
DiscordId = user.Id, UserId = user.Id,
Name = user.DisplayName Name = user.DisplayName
}; };
@@ -280,32 +296,32 @@ public partial class ActivityModule : InteractionModuleBase<SocketInteractionCon
await RespondAsync( await RespondAsync(
ephemeral: true, ephemeral: true,
embed: EmbedUtils.SuccessEmbed("Vous avez bien été inscrit pour cette activité").Build() embed: EmbedUtils.SuccessEmbed("Vous avez bien été **inscrit** pour cette activité").Build()
); );
} }
[ComponentInteraction("activity leave:*", ignoreGroupNames: true)] [ComponentInteraction("activity leave:*", ignoreGroupNames: true)]
private async Task LeaveActivity(ulong activityId) public async Task LeaveActivity(ulong activityId)
{ {
var user = (IGuildUser)Context.User; var user = (IGuildUser)Context.User;
// Check if activity exists // Check if activity exists
if (await _activitiesRepository.FindActivity(activityId) is not { } activity) if (await _activitiesRepository.FindActivity(Context.Guild.Id, activityId) is not { } activity)
{ {
await RespondAsync( await RespondAsync(
ephemeral: true, ephemeral: true,
embed: EmbedUtils.ErrorEmbed("Cette activité n'existe plus").Build() embed: EmbedUtils.ErrorEmbed("Cette activité **n'existe plus**").Build()
); );
return; return;
} }
// Check if player is in activity // Check if player is in activity
if (await _activitiesRepository.FindActivityPlayer(activityId, user.Id) is not { } activityPlayer) if (await _activitiesRepository.FindActivityPlayer(Context.Guild.Id, activityId, user.Id) is not { } activityPlayer)
{ {
await RespondAsync( await RespondAsync(
ephemeral: true, ephemeral: true,
embed: EmbedUtils.ErrorEmbed("Vous n'êtes pas inscrit à cette activité").Build() embed: EmbedUtils.ErrorEmbed("Vous n'êtes **pas inscrit** à cette activité").Build()
); );
return; return;
@@ -321,10 +337,48 @@ public partial class ActivityModule : InteractionModuleBase<SocketInteractionCon
await RespondAsync( await RespondAsync(
ephemeral: true, ephemeral: true,
embed: EmbedUtils.SuccessEmbed("Vous avez bien été désinscrit pour cette activité").Build() embed: EmbedUtils.SuccessEmbed("Vous avez bien été **désinscrit** pour cette activité").Build()
); );
} }
[ComponentInteraction("activity delete:*", ignoreGroupNames: true)]
public async Task DeleteActivity(ulong activityId)
{
var user = (SocketGuildUser) Context.User;
// Check if activity exists
if (await _activitiesRepository.FindActivity(Context.Guild.Id, activityId) is not { } activity)
{
await RespondAsync(
ephemeral: true,
embed: EmbedUtils.ErrorEmbed("Cette activité **n'existe plus**").Build()
);
return;
}
// Check if user has permission to delete this activity
if (user.Id != activity.CreatorUserId && !user.GetPermissions((IGuildChannel) Context.Channel).ManageMessages)
{
await RespondAsync(
ephemeral: true,
embed: EmbedUtils.ErrorEmbed("Vous n'avez **pas la permission** d'exécuter cette action").Build()
);
return;
}
// Remove from database
_activitiesRepository.DeleteActivity(activity);
await _activitiesRepository.SaveChanges();
// Remove thread
await Context.Guild.GetThreadChannel(activity.ThreadId).DeleteAsync();
// Delete response
await Context.Channel.DeleteMessageAsync(activityId);
}
private async Task UpdateActivityEmbed(Activity activity, ActivityUpdateReason updateReason) private async Task UpdateActivityEmbed(Activity activity, ActivityUpdateReason updateReason)
{ {
// Get channel // Get channel
@@ -358,18 +412,24 @@ public partial class ActivityModule : InteractionModuleBase<SocketInteractionCon
return new ComponentBuilder() return new ComponentBuilder()
.AddRow(new ActionRowBuilder() .AddRow(new ActionRowBuilder()
.WithButton(new ButtonBuilder() .WithButton(new ButtonBuilder()
.WithLabel("Rejoindre l'activité") .WithLabel("Rejoindre")
.WithCustomId($"activity join:{activityId}") .WithCustomId($"activity join:{activityId}")
.WithEmote(":white_check_mark:".ToEmote()) .WithEmote(":white_check_mark:".ToEmote())
.WithStyle(ButtonStyle.Primary) .WithStyle(ButtonStyle.Primary)
.WithDisabled(disabled) .WithDisabled(disabled)
) )
.WithButton(new ButtonBuilder() .WithButton(new ButtonBuilder()
.WithLabel("Se désinscrire de l'activité") .WithLabel("Se désinscrire")
.WithCustomId($"activity leave:{activityId}") .WithCustomId($"activity leave:{activityId}")
.WithEmote(":x:".ToEmote()) .WithEmote(":x:".ToEmote())
.WithStyle(ButtonStyle.Secondary) .WithStyle(ButtonStyle.Secondary)
) )
.WithButton(new ButtonBuilder()
.WithLabel("Supprimer")
.WithCustomId($"activity delete:{activityId}")
.WithEmote(":wastebasket:".ToEmote())
.WithStyle(ButtonStyle.Danger)
)
); );
} }
} }

View File

@@ -13,14 +13,14 @@ public partial class ActivityModule
{ {
if (message is IUserMessage userMessage && userMessage.Author.IsBot) if (message is IUserMessage userMessage && userMessage.Author.IsBot)
{ {
if (await _activitiesRepository.FindActivity(message.Id) is { } activity) if (await _activitiesRepository.FindActivity(Context.Guild.Id, message.Id) is { } activity)
{ {
// Generate random player // Generate random player
var player = new ActivityRolePlayer var player = new ActivityRolePlayer
{ {
Activity = activity, Activity = activity,
Name = $"Player{Random.Shared.Next(1, 100)}", Name = $"Player{Random.Shared.Next(1, 100)}",
DiscordId = (ulong) Random.Shared.NextInt64(), UserId = (ulong) Random.Shared.NextInt64(),
Roles = (PlayerRoles) Random.Shared.Next((int) (PlayerRoles.Dps | PlayerRoles.Helper | Roles = (PlayerRoles) Random.Shared.Next((int) (PlayerRoles.Dps | PlayerRoles.Helper |
PlayerRoles.Support | PlayerRoles.Tank) + 1) PlayerRoles.Support | PlayerRoles.Tank) + 1)
}; };

View File

@@ -0,0 +1,43 @@
using Discord;
using Discord.WebSocket;
namespace Cocotte.Modules.Activities;
public partial class ActivityModule
{
private string GetStartMessage(ActivityName activityName, string creatorName) =>
$"""
**― Bienvenue ―**
Bienvenue sur le thread lié à l'activité **{_activityFormatter.FormatActivityName(activityName)}** de **{creatorName}**
Ici, vous pouvez **discuter** de l'activité, mais aussi **gérer** cette activité à l'aide de diverses **commandes**.
**― Commandes ―**
- `/activite ajouter <joueur>` - **Ajoute un joueur** à cette activité
- `/activite supprimer <joueur>` - **Supprime un joueur** de cette activité
- `/activite ping` - **Ping les joueurs** inscrits à cette activité
- `/activite description` - **Modifie la description** de l'activité
- `/activite etage` - Pour l'abîme du néant et l'origine de la guerre, **modifie l'étage** de l'activité
""";
private async Task<ulong> CreateThread(ActivityName activityName, string creatorName)
{
var channel = (SocketTextChannel) Context.Channel;
var message = await GetOriginalResponseAsync();
// Create thread
var thread = await channel.CreateThreadAsync(
$"{_activityFormatter.FormatActivityName(activityName)} - {creatorName}", ThreadType.PublicThread,
ThreadArchiveDuration.OneHour,
message, true
);
// Send management message
await thread.SendMessageAsync(GetStartMessage(activityName, creatorName));
// Add activity creator
await thread.AddUserAsync((IGuildUser) Context.User);
return thread.Id;
}
}

View File

@@ -12,14 +12,14 @@ public class ActivitiesRepository
_cocotteDbContext = cocotteDbContext; _cocotteDbContext = cocotteDbContext;
} }
public async Task<Activity?> FindActivity(ulong activityId) public async Task<Activity?> FindActivity(ulong guildId, ulong activityId)
{ {
return await _cocotteDbContext.Activities.FindAsync(activityId); return await _cocotteDbContext.Activities.FindAsync(guildId, activityId);
} }
public async Task<ActivityPlayer?> FindActivityPlayer(ulong activityId, ulong playerId) public async Task<ActivityPlayer?> FindActivityPlayer(ulong guildId, ulong activityId, ulong playerId)
{ {
return await _cocotteDbContext.ActivityPlayers.FindAsync(activityId, playerId); return await _cocotteDbContext.ActivityPlayers.FindAsync(guildId, activityId, playerId);
} }
public async Task<int> ActivityPlayerCount(Activity activity) => public async Task<int> ActivityPlayerCount(Activity activity) =>
@@ -44,4 +44,9 @@ public class ActivitiesRepository
{ {
await _cocotteDbContext.SaveChangesAsync(); await _cocotteDbContext.SaveChangesAsync();
} }
public void DeleteActivity(Activity activity)
{
_cocotteDbContext.Activities.Remove(activity);
}
} }

View File

@@ -1,16 +1,21 @@
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
using Microsoft.EntityFrameworkCore;
namespace Cocotte.Modules.Activities.Models; namespace Cocotte.Modules.Activities.Models;
[PrimaryKey(nameof(GuildId), nameof(ActivityId))]
public class Activity public class Activity
{ {
[Key, DatabaseGenerated(DatabaseGeneratedOption.None)] [DatabaseGenerated(DatabaseGeneratedOption.None)]
public required ulong ActivityId { get; set; } public required ulong GuildId { get; init; }
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public required ulong ActivityId { get; init; }
public required ulong CreatorDiscordId { get; init; } public required ulong ThreadId { get; init; }
public required string CreatorDiscordName { get; set; } public required ulong CreatorUserId { get; init; }
public string? Description { get; init; } public required string CreatorDisplayName { get; init; }
public string? Description { get; set; }
public required ActivityType Type { get; init; } public required ActivityType Type { get; init; }
public required ActivityName Name { get; init; } public required ActivityName Name { get; init; }
public required bool AreRolesEnabled { get; init; } public required bool AreRolesEnabled { get; init; }
@@ -20,6 +25,6 @@ public class Activity
public override string ToString() public override string ToString()
{ {
return $"{nameof(ActivityId)}: {ActivityId}, {nameof(CreatorDiscordId)}: {CreatorDiscordId}, {nameof(Description)}: {Description}, {nameof(Type)}: {Type}, {nameof(Name)}: {Name}, {nameof(MaxPlayers)}: {MaxPlayers}"; return $"{nameof(ActivityId)}: {ActivityId}, {nameof(CreatorUserId)}: {CreatorUserId}, {nameof(Description)}: {Description}, {nameof(Type)}: {Type}, {nameof(Name)}: {Name}, {nameof(MaxPlayers)}: {MaxPlayers}";
} }
} }

View File

@@ -3,19 +3,20 @@ using Microsoft.EntityFrameworkCore;
namespace Cocotte.Modules.Activities.Models; namespace Cocotte.Modules.Activities.Models;
[PrimaryKey(nameof(ActivityId), nameof(DiscordId))] [PrimaryKey(nameof(GuildId), nameof(ActivityId), nameof(UserId))]
public class ActivityPlayer public class ActivityPlayer
{ {
[DatabaseGenerated(DatabaseGeneratedOption.None)] [DatabaseGenerated(DatabaseGeneratedOption.None)]
public required ulong DiscordId { get; init; } public required ulong UserId { get; init; }
public required string Name { get; init; } public required string Name { get; init; }
public ulong GuildId { get; set; }
public ulong ActivityId { get; init; } public ulong ActivityId { get; init; }
public required Activity Activity { get; init; } public required Activity Activity { get; init; }
public override string ToString() public override string ToString()
{ {
return $"{nameof(DiscordId)}: {DiscordId}, {nameof(Name)}: {Name}"; return $"{nameof(UserId)}: {UserId}, {nameof(Name)}: {Name}";
} }
} }

View File

@@ -0,0 +1,16 @@
using Discord;
namespace Cocotte.Utils;
public class ChannelUtils
{
public static string GetChannelLink(IGuildChannel guildChannel)
{
return GetChannelLink(guildChannel.GuildId, guildChannel.Id);
}
public static string GetChannelLink(ulong guildId, ulong channelId)
{
return $"https://discord.com/channels/{guildId}/{channelId}";
}
}