diff --git a/Commands/CommandsBase.cs b/Commands/CommandsBase.cs index 9aea3f1..3dfc7e9 100644 --- a/Commands/CommandsBase.cs +++ b/Commands/CommandsBase.cs @@ -25,16 +25,20 @@ public class CommandsBase : InteractionModuleBase { /// /// Returns a string displaying the current time in the given time zone. - /// The result begins with four numbers for sorting purposes. Must be trimmed before output. + /// The result begins with six numbers for sorting purposes. Must be trimmed before output. /// - protected static string TzPrint(string zone) { + protected static string TzPrint(string zone, bool use12hr) { var tzdb = DateTimeZoneProviders.Tzdb; DateTimeZone tz = tzdb.GetZoneOrNull(zone)!; if (tz == null) throw new Exception("Encountered unknown time zone: " + zone); var now = SystemClock.Instance.GetCurrentInstant().InZone(tz); - var sortpfx = now.ToString("MMdd", DateTimeFormatInfo.InvariantInfo); - var fullstr = now.ToString("dd'-'MMM' 'HH':'mm' 'x' (UTC'o')'", DateTimeFormatInfo.InvariantInfo); + var sortpfx = now.ToString("MMddHH", DateTimeFormatInfo.InvariantInfo); + string fullstr; + if (use12hr) { + var ap = now.ToString("tt", DateTimeFormatInfo.InvariantInfo).ToLowerInvariant(); + fullstr = now.ToString($"MMM' 'dd', 'hh':'mm'{ap} 'x' (UTC'o')'", DateTimeFormatInfo.InvariantInfo); + } else fullstr = now.ToString("dd'-'MMM', 'HH':'mm' 'x' (UTC'o')'", DateTimeFormatInfo.InvariantInfo); return $"{sortpfx}● `{fullstr}`"; } diff --git a/Commands/ConfigCommands.cs b/Commands/ConfigCommands.cs new file mode 100644 index 0000000..43c3d74 --- /dev/null +++ b/Commands/ConfigCommands.cs @@ -0,0 +1,28 @@ +using Discord.Interactions; + +namespace WorldTime.Commands; +[Group("config", HelpSettings)] +public class ConfigCommands : CommandsBase { + internal const string HelpSettings = "Configuration commands for World Time."; + internal const string HelpUse12 = "Sets whether to use the 12-hour (AM/PM) format in time zone listings."; + + [RequireGuildContext] + [SlashCommand("use-12hour", HelpUse12)] + public async Task Cmd12Hour([Summary(description: "True to enable, False to disable.")] bool setting) { + if (!IsUserAdmin((SocketGuildUser)Context.User)) { + await RespondAsync(ErrNotAllowed, ephemeral: true); + return; + } + + using var db = DbContext; + var gs = db.GuildSettings.Where(r => r.GuildId == Context.Guild.Id).SingleOrDefault(); + if (gs == null) { + gs = new() { GuildId = Context.Guild.Id }; + db.Add(gs); + } + + gs.Use12HourTime = setting; + await db.SaveChangesAsync(); + await RespondAsync($":white_check_mark: Time listing set to **{(setting ? "AM/PM" : "24 hour")}** format."); + } +} \ No newline at end of file diff --git a/Commands/ApplicationCommands.cs b/Commands/UserCommands.cs similarity index 91% rename from Commands/ApplicationCommands.cs rename to Commands/UserCommands.cs index dfd4020..d8a237e 100644 --- a/Commands/ApplicationCommands.cs +++ b/Commands/UserCommands.cs @@ -2,12 +2,16 @@ using System.Text; namespace WorldTime.Commands; -public class ApplicationCommands : CommandsBase { - const string EmbedHelpField1 = $"`/help` - {HelpHelp}\n" +public class UserCommands : CommandsBase { + const string EmbedHelpField1 = + $"`/help` - {HelpHelp}\n" + $"`/list` - {HelpList}\n" + $"`/set` - {HelpSet}\n" + $"`/remove` - {HelpRemove}"; - const string EmbedHelpField2 = $"`/set-for` - {HelpSetFor}\n`/remove-for` - {HelpRemoveFor}"; + const string EmbedHelpField2 = + $"`/config use-12hour` - {ConfigCommands.HelpUse12}\n" + + $"`/set-for` - {HelpSetFor}\n" + + $"`/remove-for` - {HelpRemoveFor}"; #region Help strings const string HelpHelp = "Displays a list of available bot commands."; @@ -67,23 +71,25 @@ public class ApplicationCommands : CommandsBase { await RespondAsync(":x: Nothing to show. Register your time zones with the bot using the `/set` command."); return; } + // Order times by popularity to limit how many are shown, group by printed name var sortedlist = new SortedDictionary>(); + var ampm = db.GuildSettings.Where(s => s.GuildId == Context.Guild.Id).SingleOrDefault()?.Use12HourTime ?? false; foreach ((string area, List users) in userlist.OrderByDescending(o => o.Value.Count).Take(20)) { // Filter further to top 20 distinct timezones, even if they are not displayed in the final result - var areaprint = TzPrint(area); + var areaprint = TzPrint(area, ampm); if (!sortedlist.ContainsKey(areaprint)) sortedlist.Add(areaprint, new List()); sortedlist[areaprint].AddRange(users); } const int MaxSingleLineLength = 750; - const int MaxSingleOutputLength = 900; + const int MaxSingleOutputLength = 3000; // Build zone listings with users var outputlines = new List(); foreach ((string area, List users) in sortedlist) { var buffer = new StringBuilder(); - buffer.Append(area[4..] + ": "); + buffer.Append(area[6..] + ": "); bool empty = true; foreach (var userid in users) { var userinstance = Context.Guild.GetUser(userid); @@ -137,7 +143,8 @@ public class ApplicationCommands : CommandsBase { return; } - var resulttext = TzPrint(result)[4..] + ": " + FormatName(parameter); + var ampm = db.GuildSettings.Where(s => s.GuildId == Context.Guild.Id).SingleOrDefault()?.Use12HourTime ?? false; + var resulttext = TzPrint(result, ampm)[6..] + ": " + FormatName(parameter); await RespondAsync(embed: new EmbedBuilder().WithDescription(resulttext).Build()); } diff --git a/Data/BotDatabaseContext.cs b/Data/BotDatabaseContext.cs index 52c4298..a3fab44 100644 --- a/Data/BotDatabaseContext.cs +++ b/Data/BotDatabaseContext.cs @@ -17,6 +17,7 @@ public class BotDatabaseContext : DbContext { } public DbSet UserEntries { get; set; } = null!; + public DbSet GuildSettings { get; set; } = null!; protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) => optionsBuilder @@ -24,9 +25,8 @@ public class BotDatabaseContext : DbContext { .UseSnakeCaseNamingConvention(); protected override void OnModelCreating(ModelBuilder modelBuilder) { - modelBuilder.Entity(entity => { - entity.HasKey(e => new { e.GuildId, e.UserId }).HasName("userdata_pkey"); - }); + modelBuilder.Entity().HasKey(e => new { e.GuildId, e.UserId }).HasName("userdata_pkey"); + modelBuilder.Entity().Property(p => p.Use12HourTime).HasDefaultValue(false); } #region Helper methods / abstractions diff --git a/Data/GuildConfiguration.cs b/Data/GuildConfiguration.cs new file mode 100644 index 0000000..6339bd7 --- /dev/null +++ b/Data/GuildConfiguration.cs @@ -0,0 +1,9 @@ +using System.ComponentModel.DataAnnotations; + +namespace WorldTime.Data; +public class GuildConfiguration { + [Key] + public ulong GuildId { get; set; } + + public bool Use12HourTime { get; set; } +} \ No newline at end of file diff --git a/Data/Migrations/20230115041447_Add12HrSetting.Designer.cs b/Data/Migrations/20230115041447_Add12HrSetting.Designer.cs new file mode 100644 index 0000000..92939fe --- /dev/null +++ b/Data/Migrations/20230115041447_Add12HrSetting.Designer.cs @@ -0,0 +1,69 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using WorldTime.Data; + +#nullable disable + +namespace WorldTime.Data.Migrations +{ + [DbContext(typeof(BotDatabaseContext))] + [Migration("20230115041447_Add12HrSetting")] + partial class Add12HrSetting + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.1") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("WorldTime.Data.GuildConfiguration", b => + { + b.Property("GuildId") + .ValueGeneratedOnAdd() + .HasColumnType("numeric(20,0)") + .HasColumnName("guild_id"); + + b.Property("Use12HourTime") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("use12hour_time"); + + b.HasKey("GuildId") + .HasName("pk_guild_settings"); + + b.ToTable("guild_settings", (string)null); + }); + + modelBuilder.Entity("WorldTime.Data.UserEntry", b => + { + b.Property("GuildId") + .HasColumnType("bigint") + .HasColumnName("guild_id"); + + b.Property("UserId") + .HasColumnType("bigint") + .HasColumnName("user_id"); + + b.Property("TimeZone") + .IsRequired() + .HasColumnType("text") + .HasColumnName("zone"); + + b.HasKey("GuildId", "UserId") + .HasName("userdata_pkey"); + + b.ToTable("userdata", (string)null); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Data/Migrations/20230115041447_Add12HrSetting.cs b/Data/Migrations/20230115041447_Add12HrSetting.cs new file mode 100644 index 0000000..0953a8c --- /dev/null +++ b/Data/Migrations/20230115041447_Add12HrSetting.cs @@ -0,0 +1,33 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace WorldTime.Data.Migrations +{ + /// + public partial class Add12HrSetting : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "guild_settings", + columns: table => new + { + guildid = table.Column(name: "guild_id", type: "numeric(20,0)", nullable: false), + use12hourtime = table.Column(name: "use12hour_time", type: "boolean", nullable: false, defaultValue: false) + }, + constraints: table => + { + table.PrimaryKey("pk_guild_settings", x => x.guildid); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "guild_settings"); + } + } +} diff --git a/Data/Migrations/BotDatabaseContextModelSnapshot.cs b/Data/Migrations/BotDatabaseContextModelSnapshot.cs index a8cfd57..63704a5 100644 --- a/Data/Migrations/BotDatabaseContextModelSnapshot.cs +++ b/Data/Migrations/BotDatabaseContextModelSnapshot.cs @@ -16,11 +16,30 @@ namespace WorldTime.Data.Migrations { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "6.0.6") + .HasAnnotation("ProductVersion", "7.0.1") .HasAnnotation("Relational:MaxIdentifierLength", 63); NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + modelBuilder.Entity("WorldTime.Data.GuildConfiguration", b => + { + b.Property("GuildId") + .ValueGeneratedOnAdd() + .HasColumnType("numeric(20,0)") + .HasColumnName("guild_id"); + + b.Property("Use12HourTime") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("use12hour_time"); + + b.HasKey("GuildId") + .HasName("pk_guild_settings"); + + b.ToTable("guild_settings", (string)null); + }); + modelBuilder.Entity("WorldTime.Data.UserEntry", b => { b.Property("GuildId")