[Activity] Add due time

This commit is contained in:
2023-03-26 00:43:15 +01:00
parent 6001a3dc24
commit cd6a9e87ad
8 changed files with 259 additions and 27 deletions

View File

@@ -0,0 +1,159 @@
// <auto-generated />
using System;
using Cocotte.Services;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace Cocotte.Migrations
{
[DbContext(typeof(CocotteDbContext))]
[Migration("20230325234255_AddActivityDueTime")]
partial class AddActivityDueTime
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "7.0.4");
modelBuilder.Entity("Cocotte.Modules.Activities.Models.Activity", b =>
{
b.Property<ulong>("GuildId")
.HasColumnType("INTEGER");
b.Property<ulong>("ChannelId")
.HasColumnType("INTEGER");
b.Property<ulong>("MessageId")
.HasColumnType("INTEGER");
b.Property<bool>("AreRolesEnabled")
.HasColumnType("INTEGER");
b.Property<DateTime>("CreationDate")
.HasColumnType("TEXT");
b.Property<string>("CreatorDisplayName")
.IsRequired()
.HasColumnType("TEXT");
b.Property<ulong>("CreatorUserId")
.HasColumnType("INTEGER");
b.Property<string>("Description")
.HasColumnType("TEXT");
b.Property<string>("Discriminator")
.IsRequired()
.HasColumnType("TEXT");
b.Property<TimeOnly?>("DueTime")
.HasColumnType("TEXT");
b.Property<uint>("MaxPlayers")
.HasColumnType("INTEGER");
b.Property<int>("Name")
.HasColumnType("INTEGER");
b.Property<ulong>("ThreadId")
.HasColumnType("INTEGER");
b.Property<int>("Type")
.HasColumnType("INTEGER");
b.HasKey("GuildId", "ChannelId", "MessageId");
b.HasIndex("ThreadId");
b.ToTable("Activities");
b.HasDiscriminator<string>("Discriminator").HasValue("Activity");
b.UseTphMappingStrategy();
});
modelBuilder.Entity("Cocotte.Modules.Activities.Models.ActivityPlayer", b =>
{
b.Property<ulong>("GuildId")
.HasColumnType("INTEGER");
b.Property<ulong>("ChannelId")
.HasColumnType("INTEGER");
b.Property<ulong>("MessageId")
.HasColumnType("INTEGER");
b.Property<ulong>("UserId")
.HasColumnType("INTEGER");
b.Property<string>("Discriminator")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("GuildId", "ChannelId", "MessageId", "UserId");
b.ToTable("ActivityPlayers");
b.HasDiscriminator<string>("Discriminator").HasValue("ActivityPlayer");
b.UseTphMappingStrategy();
});
modelBuilder.Entity("Cocotte.Modules.Activities.Models.InterstellarActivity", b =>
{
b.HasBaseType("Cocotte.Modules.Activities.Models.Activity");
b.Property<int>("Color")
.HasColumnType("INTEGER");
b.HasDiscriminator().HasValue("InterstellarActivity");
});
modelBuilder.Entity("Cocotte.Modules.Activities.Models.StagedActivity", b =>
{
b.HasBaseType("Cocotte.Modules.Activities.Models.Activity");
b.Property<uint>("Stage")
.HasColumnType("INTEGER");
b.HasDiscriminator().HasValue("StagedActivity");
});
modelBuilder.Entity("Cocotte.Modules.Activities.Models.ActivityRolePlayer", b =>
{
b.HasBaseType("Cocotte.Modules.Activities.Models.ActivityPlayer");
b.Property<byte>("Roles")
.HasColumnType("INTEGER");
b.HasDiscriminator().HasValue("ActivityRolePlayer");
});
modelBuilder.Entity("Cocotte.Modules.Activities.Models.ActivityPlayer", b =>
{
b.HasOne("Cocotte.Modules.Activities.Models.Activity", "Activity")
.WithMany("ActivityPlayers")
.HasForeignKey("GuildId", "ChannelId", "MessageId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Activity");
});
modelBuilder.Entity("Cocotte.Modules.Activities.Models.Activity", b =>
{
b.Navigation("ActivityPlayers");
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -0,0 +1,29 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Cocotte.Migrations
{
/// <inheritdoc />
public partial class AddActivityDueTime : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<TimeOnly>(
name: "DueTime",
table: "Activities",
type: "TEXT",
nullable: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "DueTime",
table: "Activities");
}
}
}

View File

@@ -48,6 +48,9 @@ namespace Cocotte.Migrations
.IsRequired()
.HasColumnType("TEXT");
b.Property<TimeOnly?>("DueTime")
.HasColumnType("TEXT");
b.Property<uint>("MaxPlayers")
.HasColumnType("INTEGER");

View File

@@ -66,13 +66,27 @@ public class ActivityFormatter
$"{FormatActivityName(activity.Name)} ({players.Count}/{activity.MaxPlayers})"
};
// Build description
var descriptionBuilder = new StringBuilder();
// Add time if specified
if (activity.DueTime is { } time)
{
descriptionBuilder.AppendLine($"**:clock2: {TimestampTag.FormatFromDateTime(DateTime.Today.WithTimeOnly(time), TimestampTagStyles.ShortTime)}**");
}
else
{
descriptionBuilder.AppendLine($"**:clock2: Maintenant**");
}
// Add generic message or specified activity description
descriptionBuilder.AppendLine(
string.IsNullOrWhiteSpace(activity.Description)
? $"Rejoignez l'activité de {MentionUtils.MentionUser(activity.CreatorUserId)}"
: activity.Description
);
// Add thread link
descriptionBuilder.AppendLine();
descriptionBuilder.Append($"**[Fil associé]({ChannelUtils.GetChannelLink(activity.GuildId, activity.ThreadId)})**");

View File

@@ -53,91 +53,108 @@ public partial class ActivityModule : InteractionModuleBase<SocketInteractionCon
[SlashCommand("abime-néant", "Créer un groupe pour l'Abîme du Néant")]
[Alias("abime", "abyss")]
public async Task ActivityVoidAbyss([Summary("étage", "A quel étage vous êtes")] [MinValue(1), MaxValue(6)] uint stage, [Summary("description", "Message accompagnant la demande de groupe")] string description = "")
public async Task ActivityVoidAbyss([Summary("étage", "A quel étage vous êtes")] [MinValue(1), MaxValue(6)] uint stage, [Summary("heure", "Heure à laquelle l'activité est prévue")] string? timeInput = null, [Summary("description", "Message accompagnant la demande de groupe")] string description = "")
{
await CreateActivity(ActivityName.Abyss, description, stage: stage);
await CreateActivity(ActivityName.Abyss, timeInput, description, stage: stage);
}
[SlashCommand("origine-guerre", "Créer un groupe pour l'Origine de la guerre")]
[Alias("origine", "OOW")]
public async Task ActivityOrigins([Summary("étage", "A quel étage vous êtes")] [MinValue(1), MaxValue(25)] uint stage, [Summary("description", "Message accompagnant la demande de groupe")] string description = "")
public async Task ActivityOrigins([Summary("étage", "A quel étage vous êtes")] [MinValue(1), MaxValue(25)] uint stage, [Summary("heure", "Heure à laquelle l'activité est prévue")] string? timeInput = null, [Summary("description", "Message accompagnant la demande de groupe")] string description = "")
{
await CreateActivity(ActivityName.OriginsOfWar, description, stage: stage);
await CreateActivity(ActivityName.OriginsOfWar, timeInput, description, stage: stage);
}
[SlashCommand("raids", "Créer un groupe pour les Raids")]
public async Task ActivityRaids([Summary("description", "Message accompagnant la demande de groupe")] string description = "")
public async Task ActivityRaids([Summary("heure", "Heure à laquelle l'activité est prévue")] string? timeInput = null, [Summary("description", "Message accompagnant la demande de groupe")] string description = "")
{
await CreateActivity(ActivityName.Raids, description);
await CreateActivity(ActivityName.Raids, timeInput, description);
}
[SlashCommand("conflit-frontalier", "Créer un groupe pour Conflit frontalier")]
[Alias("conflit", "FC")]
public async Task ActivityFrontierClash([Summary("description", "Message accompagnant la demande de groupe")] string description = "")
public async Task ActivityFrontierClash([Summary("heure", "Heure à laquelle l'activité est prévue")] string? timeInput = null, [Summary("description", "Message accompagnant la demande de groupe")] string description = "")
{
await CreateActivity(ActivityName.FrontierClash, description);
await CreateActivity(ActivityName.FrontierClash, timeInput, description);
}
[SlashCommand("failles-neant", "Créer un groupe pour les Failles du néant")]
[Alias("failles", "rift")]
public async Task ActivityVoidRift([Summary("description", "Message accompagnant la demande de groupe")] string description = "")
public async Task ActivityVoidRift([Summary("heure", "Heure à laquelle l'activité est prévue")] string? timeInput = null, [Summary("description", "Message accompagnant la demande de groupe")] string description = "")
{
await CreateActivity(ActivityName.VoidRift, description);
await CreateActivity(ActivityName.VoidRift, timeInput, description);
}
[SlashCommand("operations-conjointes", "Créer un groupe pour les Opérations conjointes")]
[Alias("operations", "JO")]
public async Task ActivityJointOperation([Summary("description", "Message accompagnant la demande de groupe")] string description = "")
public async Task ActivityJointOperation([Summary("heure", "Heure à laquelle l'activité est prévue")] string? timeInput = null, [Summary("description", "Message accompagnant la demande de groupe")] string description = "")
{
await CreateActivity(ActivityName.JointOperation, description);
await CreateActivity(ActivityName.JointOperation, timeInput, description);
}
[SlashCommand("portes-interstellaires", "Créer un groupe pour les Portes interstellaires")]
[Alias("portes")]
public async Task ActivityInterstellarExploration([Summary("couleur", "De quel couleur de matériaux s'agît-il")] InterstellarColor color, [Summary("description", "Message accompagnant la demande de groupe")] string description = "")
public async Task ActivityInterstellarExploration([Summary("couleur", "De quel couleur de matériaux s'agît-il")] InterstellarColor color, [Summary("heure", "Heure à laquelle l'activité est prévue")] string? timeInput = null, [Summary("description", "Message accompagnant la demande de groupe")] string description = "")
{
await CreateActivity(ActivityName.InterstellarExploration, description, areRolesEnabled: false, interstellarColor: color);
await CreateActivity(ActivityName.InterstellarExploration, timeInput, description, areRolesEnabled: false, interstellarColor: color);
}
[SlashCommand("3v3", "Créer un groupe pour le 3v3 (Échapper au destin)")]
[Alias("BR")]
public async Task ActivityBreakFromDestiny([Summary("description", "Message accompagnant la demande de groupe")] string description = "")
public async Task ActivityBreakFromDestiny([Summary("heure", "Heure à laquelle l'activité est prévue")] string? timeInput = null, [Summary("description", "Message accompagnant la demande de groupe")] string description = "")
{
await CreateActivity(ActivityName.BreakFromDestiny, description, areRolesEnabled: false);
await CreateActivity(ActivityName.BreakFromDestiny, timeInput, description, areRolesEnabled: false);
}
[SlashCommand("8v8", "Créer un groupe pour le 8v8 (Abîme critique)")]
[Alias("critical")]
public async Task ActivityCriticalAbyss([Summary("description", "Message accompagnant la demande de groupe")] string description = "")
public async Task ActivityCriticalAbyss([Summary("heure", "Heure à laquelle l'activité est prévue")] string? timeInput = null, [Summary("description", "Message accompagnant la demande de groupe")] string description = "")
{
await CreateActivity(ActivityName.CriticalAbyss, description);
await CreateActivity(ActivityName.CriticalAbyss, timeInput, description);
}
[SlashCommand("evenement", "Créer un groupe pour les évènements")]
[Alias("event")]
public async Task ActivityEvent([Summary("joueurs", "Nombre de joueurs maximum pour cette activité")] [MinValue(2), MaxValue(16)] uint maxPlayers = 8, [Summary("description", "Message accompagnant la demande de groupe")] string description = "")
public async Task ActivityEvent([Summary("joueurs", "Nombre de joueurs maximum pour cette activité")] [MinValue(2), MaxValue(16)] uint maxPlayers = 8, [Summary("heure", "Heure à laquelle l'activité est prévue")] string? timeInput = null, [Summary("description", "Message accompagnant la demande de groupe")] string description = "")
{
await CreateActivity(ActivityName.Event, description, areRolesEnabled: false, maxPlayers: maxPlayers);
await CreateActivity(ActivityName.Event, timeInput, description, areRolesEnabled: false, maxPlayers: maxPlayers);
}
[SlashCommand("peche", "Créer un groupe pour de la pêche")]
[Alias("fishing")]
public async Task ActivityFishing([Summary("description", "Message accompagnant la demande de groupe")] string description = "")
public async Task ActivityFishing([Summary("heure", "Heure à laquelle l'activité est prévue")] string? timeInput = null, [Summary("description", "Message accompagnant la demande de groupe")] string description = "")
{
await CreateActivity(ActivityName.Fishing, description, areRolesEnabled: false);
await CreateActivity(ActivityName.Fishing, timeInput, description, areRolesEnabled: false);
}
[SlashCommand("course", "Créer un groupe pour les courses de Mirroria")]
[Alias("BR")]
public async Task ActivityMirroriaRace([Summary("description", "Message accompagnant la demande de groupe")] string description = "")
public async Task ActivityMirroriaRace([Summary("heure", "Heure à laquelle l'activité est prévue")] string? timeInput = null, [Summary("description", "Message accompagnant la demande de groupe")] string description = "")
{
await CreateActivity(ActivityName.MirroriaRace, description, areRolesEnabled: false);
await CreateActivity(ActivityName.MirroriaRace, timeInput, 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? timeInput, string description, bool areRolesEnabled = true, uint? maxPlayers = null, uint? stage = null, InterstellarColor? interstellarColor = null)
{
// Check time if it's specified
TimeOnly? time = null;
if (timeInput is not null)
{
if (!TimeOnly.TryParse(timeInput, out var parsedTime))
{
await RespondAsync(
ephemeral: true,
embed: EmbedUtils.ErrorEmbed("**Heure invalide**, essayez avec le **format** `heure:minutes`\nPar exemple: `15:30`").Build()
);
return;
}
time = parsedTime;
}
_logger.LogTrace("{User} is creating activity {Activity}", User.DisplayName, activityName);
// Activities are identified using their original message id
@@ -163,6 +180,7 @@ public partial class ActivityModule : InteractionModuleBase<SocketInteractionCon
CreatorUserId = Context.User.Id,
CreatorDisplayName = ((SocketGuildUser) Context.User).DisplayName,
Description = description,
DueTime = time,
Type = activityType,
Name = activityName,
AreRolesEnabled = areRolesEnabled,
@@ -181,6 +199,7 @@ public partial class ActivityModule : InteractionModuleBase<SocketInteractionCon
CreatorUserId = Context.User.Id,
CreatorDisplayName = ((SocketGuildUser) Context.User).DisplayName,
Description = description,
DueTime = time,
Type = activityType,
Name = activityName,
AreRolesEnabled = false,
@@ -199,6 +218,7 @@ public partial class ActivityModule : InteractionModuleBase<SocketInteractionCon
CreatorUserId = Context.User.Id,
CreatorDisplayName = ((SocketGuildUser) Context.User).DisplayName,
Description = description,
DueTime = time,
Type = activityType,
Name = activityName,
AreRolesEnabled = areRolesEnabled,

View File

@@ -1,5 +1,4 @@
using System.Diagnostics.CodeAnalysis;
using System.Text;
using System.Text;
using Cocotte.Modules.Activities.Models;
using Cocotte.Utils;
using Discord;

View File

@@ -17,6 +17,7 @@ public class Activity
public required ulong ThreadId { get; init; }
public required ulong CreatorUserId { get; init; }
public required string CreatorDisplayName { get; init; }
public TimeOnly? DueTime { get; init; }
public string? Description { get; set; }
public required ActivityType Type { get; init; }
public required ActivityName Name { get; init; }

View File

@@ -13,4 +13,11 @@ public static class DateTimeUtils
int offset = (7 - c + d) % 7;
return offset;
}
public static DateTime WithTimeOnly(this DateTime dateTime, TimeOnly timeOnly)
{
return new DateTime(
dateTime.Year, dateTime.Month, dateTime.Day, timeOnly.Hour, timeOnly.Minute, timeOnly.Second
);
}
}