diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json
new file mode 100644
index 0000000..677ed3f
--- /dev/null
+++ b/.config/dotnet-tools.json
@@ -0,0 +1,12 @@
+{
+ "version": 1,
+ "isRoot": true,
+ "tools": {
+ "dotnet-ef": {
+ "version": "8.0.4",
+ "commands": [
+ "dotnet-ef"
+ ]
+ }
+ }
+}
\ No newline at end of file
diff --git a/.editorconfig b/.editorconfig
index 0b8c1b3..c6ff09e 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -13,13 +13,13 @@ tab_width = 4
# New line preferences
end_of_line = crlf
-insert_final_newline = false
+insert_final_newline = true
#### .NET Coding Conventions ####
# Organize usings
dotnet_separate_import_directive_groups = false
-dotnet_sort_system_directives_first = false
+dotnet_sort_system_directives_first = true
file_header_template = unset
# this. and Me. preferences
@@ -63,7 +63,7 @@ dotnet_style_prefer_simplified_interpolation = true
dotnet_style_readonly_field = true
# Parameter preferences
-dotnet_code_quality_unused_parameters = all
+dotnet_code_quality_unused_parameters = true
# Suppression preferences
dotnet_remove_unnecessary_suppression_exclusions = none
@@ -77,7 +77,7 @@ dotnet_style_allow_statement_immediately_after_block_experimental = true
# var preferences
csharp_style_var_elsewhere = false:silent
csharp_style_var_for_built_in_types = true:suggestion
-csharp_style_var_when_type_is_apparent = false:silent
+csharp_style_var_when_type_is_apparent = true:suggestion
# Expression-bodied members
csharp_style_expression_bodied_accessors = true:silent
@@ -101,7 +101,6 @@ csharp_style_conditional_delegate_call = true:suggestion
# Modifier preferences
csharp_prefer_static_local_function = true:suggestion
-csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async
# Code-block preferences
csharp_prefer_braces = when_multiline:silent
@@ -221,38 +220,4 @@ csharp_style_prefer_local_over_anonymous_function = true:suggestion
csharp_style_prefer_tuple_swap = true:suggestion
csharp_style_prefer_extended_property_pattern = true:suggestion
-[*.{cs,vb}]
-dotnet_style_operator_placement_when_wrapping = beginning_of_line
-tab_width = 4
-indent_size = 4
-end_of_line = crlf
-dotnet_style_coalesce_expression = true:suggestion
-dotnet_style_null_propagation = true:suggestion
-dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
-dotnet_style_prefer_auto_properties = true:suggestion
-dotnet_style_object_initializer = true:suggestion
-dotnet_style_collection_initializer = true:suggestion
-dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
-dotnet_style_prefer_conditional_expression_over_assignment = true:silent
-dotnet_style_prefer_conditional_expression_over_return = true:silent
-dotnet_style_explicit_tuple_names = true:suggestion
-dotnet_style_namespace_match_folder = true:suggestion
-dotnet_style_prefer_inferred_tuple_names = true:suggestion
-dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
-dotnet_style_prefer_compound_assignment = true:suggestion
-dotnet_style_prefer_simplified_interpolation = true:suggestion
-dotnet_style_readonly_field = true:suggestion
-dotnet_style_predefined_type_for_locals_parameters_members = true:silent
-dotnet_style_predefined_type_for_member_access = true:silent
-dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent
-dotnet_style_allow_multiple_blank_lines_experimental = false:suggestion
-dotnet_style_allow_statement_immediately_after_block_experimental = true:silent
-dotnet_code_quality_unused_parameters = all:suggestion
-dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent
-dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent
-dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent
-dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent
-dotnet_style_qualification_for_field = false:silent
-dotnet_style_qualification_for_property = false:silent
-dotnet_style_qualification_for_method = false:silent
-dotnet_style_qualification_for_event = false:silent
\ No newline at end of file
+csharp_style_prefer_primary_constructors = true:suggestion
diff --git a/.vscode/launch.json b/.vscode/launch.json
index 23c8edb..42ff7f8 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -10,8 +10,8 @@
"request": "launch",
"preLaunchTask": "build",
// If you have changed target frameworks, make sure to update the program path.
- "program": "${workspaceFolder}/bin/Debug/net6.0/BirthdayBot.dll",
- "args": [ "-c", "${workspaceFolder}/bin/Debug/net6.0/settings.json" ],
+ "program": "${workspaceFolder}/bin/Debug/net8.0/BirthdayBot.dll",
+ "args": [ "-c", "${workspaceFolder}/bin/Debug/settings.json" ],
"cwd": "${workspaceFolder}",
// For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console
"console": "internalConsole",
diff --git a/ApplicationCommands/BirthdayModule.cs b/ApplicationCommands/BirthdayModule.cs
index 43429ef..2f5f670 100644
--- a/ApplicationCommands/BirthdayModule.cs
+++ b/ApplicationCommands/BirthdayModule.cs
@@ -4,7 +4,7 @@ using System.Text;
namespace BirthdayBot.ApplicationCommands;
[Group("birthday", HelpCmdBirthday)]
-[EnabledInDm(false)]
+[CommandContextType(InteractionContextType.Guild)]
public class BirthdayModule : BotModuleBase {
public const string HelpCmdBirthday = "Commands relating to birthdays.";
public const string HelpCmdSetDate = "Sets or updates your birthday.";
diff --git a/ApplicationCommands/BirthdayOverrideModule.cs b/ApplicationCommands/BirthdayOverrideModule.cs
index 38c199d..c261f94 100644
--- a/ApplicationCommands/BirthdayOverrideModule.cs
+++ b/ApplicationCommands/BirthdayOverrideModule.cs
@@ -5,7 +5,7 @@ using static BirthdayBot.Common;
namespace BirthdayBot.ApplicationCommands;
[Group("override", HelpCmdOverride)]
[DefaultMemberPermissions(GuildPermission.ManageGuild)]
-[EnabledInDm(false)]
+[CommandContextType(InteractionContextType.Guild)]
public class BirthdayOverrideModule : BotModuleBase {
public const string HelpCmdOverride = "Commands to set options for other users.";
const string HelpOptOvTarget = "The user whose data to modify.";
@@ -14,8 +14,8 @@ public class BirthdayOverrideModule : BotModuleBase {
// TODO possible to use a common base class for shared functionality instead?
[SlashCommand("set-birthday", "Set a user's birthday on their behalf.")]
- public async Task OvSetBirthday([Summary(description: HelpOptOvTarget)]SocketGuildUser target,
- [Summary(description: HelpOptDate)]string date) {
+ public async Task OvSetBirthday([Summary(description: HelpOptOvTarget)] SocketGuildUser target,
+ [Summary(description: HelpOptDate)] string date) {
int inmonth, inday;
try {
(inmonth, inday) = ParseDate(date);
@@ -43,8 +43,8 @@ public class BirthdayOverrideModule : BotModuleBase {
}
[SlashCommand("set-timezone", "Set a user's time zone on their behalf.")]
- public async Task OvSetTimezone([Summary(description: HelpOptOvTarget)]SocketGuildUser target,
- [Summary(description: HelpOptZone)]string zone) {
+ public async Task OvSetTimezone([Summary(description: HelpOptOvTarget)] SocketGuildUser target,
+ [Summary(description: HelpOptZone)] string zone) {
using var db = new BotDatabaseContext();
var user = target.GetUserEntryOrNew(db);
@@ -68,7 +68,7 @@ public class BirthdayOverrideModule : BotModuleBase {
}
[SlashCommand("remove-birthday", "Remove a user's birthday information on their behalf.")]
- public async Task OvRemove([Summary(description: HelpOptOvTarget)]SocketGuildUser target) {
+ public async Task OvRemove([Summary(description: HelpOptOvTarget)] SocketGuildUser target) {
using var db = new BotDatabaseContext();
var user = target.GetUserEntryOrNew(db);
if (!user.IsNew) {
diff --git a/ApplicationCommands/BotModuleBase.cs b/ApplicationCommands/BotModuleBase.cs
index 2a6ddd1..7660576 100644
--- a/ApplicationCommands/BotModuleBase.cs
+++ b/ApplicationCommands/BotModuleBase.cs
@@ -10,7 +10,7 @@ namespace BirthdayBot.ApplicationCommands;
///
/// Base class for our interaction module classes. Contains common data for use in implementing classes.
///
-public abstract class BotModuleBase : InteractionModuleBase {
+public abstract partial class BotModuleBase : InteractionModuleBase {
protected const string MemberCacheEmptyError = ":warning: Please try the command again.";
public const string AccessDeniedError = ":warning: You are not allowed to run this command.";
@@ -65,8 +65,10 @@ public abstract class BotModuleBase : InteractionModuleBase\d{1,2})[ -](?[A-Za-z]+)$", RegexOptions.Compiled);
- private static readonly Regex DateParse2 = new(@"^(?[A-Za-z]+)[ -](?\d{1,2})$", RegexOptions.Compiled);
+ [GeneratedRegex(@"^(?\d{1,2})[ -](?[A-Za-z]+)$")]
+ private static partial Regex DateParser1();
+ [GeneratedRegex(@"^(?[A-Za-z]+)[ -](?\d{1,2})$")]
+ private static partial Regex DateParser2();
///
/// Parses a date input.
@@ -76,10 +78,10 @@ public abstract class BotModuleBase : InteractionModuleBase
protected static (int, int) ParseDate(string dateInput) {
- var m = DateParse1.Match(dateInput);
+ var m = DateParser1().Match(dateInput);
if (!m.Success) {
// Flip the fields around, try again
- m = DateParse2.Match(dateInput);
+ m = DateParser2().Match(dateInput);
if (!m.Success) throw new FormatException(FormatError);
}
diff --git a/ApplicationCommands/ConfigModule.cs b/ApplicationCommands/ConfigModule.cs
index 2ce338a..81c55a1 100644
--- a/ApplicationCommands/ConfigModule.cs
+++ b/ApplicationCommands/ConfigModule.cs
@@ -5,7 +5,7 @@ using System.Text;
namespace BirthdayBot.ApplicationCommands;
[Group("config", HelpCmdConfig)]
[DefaultMemberPermissions(GuildPermission.ManageGuild)]
-[EnabledInDm(false)]
+[CommandContextType(InteractionContextType.Guild)]
public class ConfigModule : BotModuleBase {
public const string HelpCmdConfig = "Configure basic settings for the bot.";
public const string HelpCmdAnnounce = "Settings regarding birthday announcements.";
@@ -112,7 +112,7 @@ public class ConfigModule : BotModuleBase {
}
[SlashCommand("set-ping", HelpSubCmdPing)]
- public async Task CmdSetPing([Summary(description: "Set True to ping users, False to display them normally.")]bool option) {
+ public async Task CmdSetPing([Summary(description: "Set True to ping users, False to display them normally.")] bool option) {
await DoDatabaseUpdate(Context, s => s.AnnouncePing = option);
await RespondAsync($":white_check_mark: Announcement pings are now **{(option ? "on" : "off")}**.").ConfigureAwait(false);
}
@@ -131,8 +131,8 @@ public class ConfigModule : BotModuleBase {
[SlashCommand("check", HelpCmdCheck)]
public async Task CmdCheck() {
static string DoTestFor(string label, Func test)
- => $"{label}: { (test() ? ":white_check_mark: Yes" : ":x: No") }";
-
+ => $"{label}: {(test() ? ":white_check_mark: Yes" : ":x: No")}";
+
var guild = Context.Guild;
using var db = new BotDatabaseContext();
var guildconf = guild.GetConfigOrNew(db);
@@ -141,8 +141,8 @@ public class ConfigModule : BotModuleBase {
var result = new StringBuilder();
result.AppendLine($"Server ID: `{guild.Id}` | Bot shard ID: `{Shard.ShardId:00}`");
- result.AppendLine($"Number of registered birthdays: `{ guildconf.UserEntries.Count }`");
- result.AppendLine($"Server time zone: `{ guildconf.GuildTimeZone ?? "Not set - using UTC" }`");
+ result.AppendLine($"Number of registered birthdays: `{guildconf.UserEntries?.Count ?? 0}`");
+ result.AppendLine($"Server time zone: `{guildconf.GuildTimeZone ?? "Not set - using UTC"}`");
result.AppendLine();
var hasMembers = Common.HasMostMembersDownloaded(guild);
@@ -152,7 +152,8 @@ public class ConfigModule : BotModuleBase {
result.Append(DoTestFor("Birthday processing", delegate {
if (!hasMembers) return false;
if (guildconf.IsNew) return false;
- bdayCount = BackgroundServices.BirthdayRoleUpdate.GetGuildCurrentBirthdays(guildconf.UserEntries, guildconf.GuildTimeZone).Count;
+ bdayCount = BackgroundServices.BirthdayRoleUpdate
+ .GetGuildCurrentBirthdays(guildconf.UserEntries!, guildconf.GuildTimeZone).Count;
return true;
}));
if (!hasMembers) result.AppendLine(" - Previous step failed.");
@@ -180,7 +181,7 @@ public class ConfigModule : BotModuleBase {
return announcech != null;
}));
var disp = announcech == null ? "announcement channel" : $"<#{announcech.Id}>";
- result.AppendLine(DoTestFor($"(Optional) Bot can send messages into { disp }", delegate {
+ result.AppendLine(DoTestFor($"(Optional) Bot can send messages into {disp}", delegate {
if (announcech == null) return false;
return guild.CurrentUser.GetPermissions(announcech).SendMessages;
}));
diff --git a/ApplicationCommands/ExportModule.cs b/ApplicationCommands/ExportModule.cs
index 815ac4c..365f185 100644
--- a/ApplicationCommands/ExportModule.cs
+++ b/ApplicationCommands/ExportModule.cs
@@ -7,7 +7,7 @@ public class ExportModule : BotModuleBase {
[SlashCommand("export-birthdays", HelpCmdExport)]
[DefaultMemberPermissions(GuildPermission.ManageGuild)]
- [EnabledInDm(false)]
+ [CommandContextType(InteractionContextType.Guild)]
public async Task CmdExport([Summary(description: "Specify whether to export the list in CSV format.")] bool asCsv = false) {
if (!await HasMemberCacheAsync(Context.Guild)) {
await RespondAsync(MemberCacheEmptyError, ephemeral: true);
@@ -28,7 +28,7 @@ public class ExportModule : BotModuleBase {
await RespondWithFileAsync(fileoutput, filename, text: $"Exported {bdlist.Count} birthdays to file.");
}
- private static Stream ListExportNormal(SocketGuild guild, IEnumerable list) {
+ private static MemoryStream ListExportNormal(SocketGuild guild, IEnumerable list) {
// Output: "● Mon-dd: (user ID) Username [ - Nickname: (nickname)]"
var result = new MemoryStream();
var writer = new StreamWriter(result, Encoding.UTF8);
@@ -52,7 +52,7 @@ public class ExportModule : BotModuleBase {
return result;
}
- private static Stream ListExportCsv(SocketGuild guild, IEnumerable list) {
+ private static MemoryStream ListExportCsv(SocketGuild guild, IEnumerable list) {
// Output: User ID, Username, Nickname, Month-Day, Month, Day
var result = new MemoryStream();
var writer = new StreamWriter(result, Encoding.UTF8);
diff --git a/ApplicationCommands/HelpModule.cs b/ApplicationCommands/HelpModule.cs
index 3da4bb2..fc5a313 100644
--- a/ApplicationCommands/HelpModule.cs
+++ b/ApplicationCommands/HelpModule.cs
@@ -1,7 +1,7 @@
using Discord.Interactions;
namespace BirthdayBot.ApplicationCommands;
-[EnabledInDm(true)]
+[CommandContextType(InteractionContextType.Guild, InteractionContextType.BotDm)]
public class HelpModule : BotModuleBase {
private const string TopMessage =
"Thank you for using Birthday Bot!\n" +
diff --git a/BackgroundServices/AutoUserDownload.cs b/BackgroundServices/AutoUserDownload.cs
index a9f8e15..cae178c 100644
--- a/BackgroundServices/AutoUserDownload.cs
+++ b/BackgroundServices/AutoUserDownload.cs
@@ -8,7 +8,7 @@ namespace BirthdayBot.BackgroundServices;
class AutoUserDownload : BackgroundService {
private static readonly TimeSpan RequestTimeout = ShardManager.DeadShardThreshold / 3;
- private readonly HashSet _skippedGuilds = new();
+ private readonly HashSet _skippedGuilds = [];
public AutoUserDownload(ShardInstance instance) : base(instance)
=> Shard.DiscordClient.Disconnected += OnDisconnect;
@@ -26,21 +26,20 @@ class AutoUserDownload : BackgroundService {
.Select(g => g.Id)
.ToHashSet();
// ...and if the guild contains any user data
- IEnumerable mustFetch;
+ HashSet mustFetch;
try {
await ConcurrentSemaphore.WaitAsync(token);
using var db = new BotDatabaseContext();
- mustFetch = db.UserEntries.AsNoTracking()
+ mustFetch = [.. db.UserEntries.AsNoTracking()
.Where(e => incompleteCaches.Contains(e.GuildId))
.Select(e => e.GuildId)
- .Where(e => !_skippedGuilds.Contains(e))
- .ToHashSet();
+ .Where(e => !_skippedGuilds.Contains(e))];
} finally {
try {
ConcurrentSemaphore.Release();
} catch (ObjectDisposedException) { }
}
-
+
var processed = 0;
var processStartTime = DateTimeOffset.UtcNow;
foreach (var item in mustFetch) {
@@ -67,7 +66,7 @@ class AutoUserDownload : BackgroundService {
break;
} else if (!dl.IsCompletedSuccessfully) {
Log($"Task unresponsive, will skip (ID {guild.Id}, with {guild.MemberCount} members).");
- _skippedGuilds.Add(guild.Id);
+ _skippedGuilds.Add(guild.Id);
continue;
}
}
diff --git a/BackgroundServices/BirthdayRoleUpdate.cs b/BackgroundServices/BirthdayRoleUpdate.cs
index ec11c1a..02f01c6 100644
--- a/BackgroundServices/BirthdayRoleUpdate.cs
+++ b/BackgroundServices/BirthdayRoleUpdate.cs
@@ -7,9 +7,7 @@ namespace BirthdayBot.BackgroundServices;
/// Core automatic functionality of the bot. Manages role memberships based on birthday information,
/// and optionally sends the announcement message to appropriate guilds.
///
-class BirthdayRoleUpdate : BackgroundService {
- public BirthdayRoleUpdate(ShardInstance instance) : base(instance) { }
-
+class BirthdayRoleUpdate(ShardInstance instance) : BackgroundService(instance) {
///
/// Processes birthday updates for all available guilds synchronously.
///
@@ -94,7 +92,7 @@ class BirthdayRoleUpdate : BackgroundService {
// Special case: If user's birthday is 29-Feb and it's currently not a leap year, check against 1-Mar
if (!DateTime.IsLeapYear(checkNow.Year) && record.BirthMonth == 2 && record.BirthDay == 29) {
if (checkNow.Month == 3 && checkNow.Day == 1) birthdayUsers.Add(record.UserId);
- } else if (record.BirthMonth == checkNow.Month && record.BirthDay== checkNow.Day) {
+ } else if (record.BirthMonth == checkNow.Month && record.BirthDay == checkNow.Day) {
birthdayUsers.Add(record.UserId);
}
}
diff --git a/BackgroundServices/DataRetention.cs b/BackgroundServices/DataRetention.cs
index 76b1b83..76f4ace 100644
--- a/BackgroundServices/DataRetention.cs
+++ b/BackgroundServices/DataRetention.cs
@@ -13,9 +13,8 @@ class DataRetention : BackgroundService {
const int StaleGuildThreshold = 180;
const int StaleUserThreashold = 360;
- public DataRetention(ShardInstance instance) : base(instance) {
- ProcessInterval = 21600 / Shard.Config.BackgroundInterval; // Process about once per six hours
- }
+ public DataRetention(ShardInstance instance) : base(instance)
+ => ProcessInterval = 21600 / Shard.Config.BackgroundInterval; // Process about once per six hours
public override async Task OnTick(int tickCount, CancellationToken token) {
// Run only a subset of shards each time, each running every ProcessInterval ticks.
@@ -60,7 +59,7 @@ class DataRetention : BackgroundService {
.Where(gu => localGuilds.Contains(gu.GuildId))
.Where(gu => now - TimeSpan.FromDays(StaleUserThreashold) > gu.LastSeen)
.ExecuteDeleteAsync();
-
+
// Build report
var resultText = new StringBuilder();
resultText.Append($"Updated {updatedGuilds} guilds, {updatedUsers} users.");
diff --git a/BirthdayBot.csproj b/BirthdayBot.csproj
index fa95ff5..94762a6 100644
--- a/BirthdayBot.csproj
+++ b/BirthdayBot.csproj
@@ -1,12 +1,14 @@
+ 3.5.3
+ NoiTheCat
+
Exe
- net6.0
+ net8.0
enable
enable
- 3.5.2
- NoiTheCat
+ en
@@ -22,17 +24,17 @@
-
-
-
+
+
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
-
-
-
+
+
+
diff --git a/Common.cs b/Common.cs
index 9096c98..3b10f22 100644
--- a/Common.cs
+++ b/Common.cs
@@ -21,8 +21,8 @@ static class Common {
if (member.DiscriminatorValue == 0) {
var username = escapeFormattingCharacters(member.GlobalName ?? member.Username);
- if (member.Nickname != null) {
- return $"{escapeFormattingCharacters(member.Nickname)} ({username})";
+ if (member.Nickname != null) {
+ return $"{escapeFormattingCharacters(member.Nickname)} ({username})";
}
return username;
} else {
diff --git a/Configuration.cs b/Configuration.cs
index ac1f04b..a8fdb63 100644
--- a/Configuration.cs
+++ b/Configuration.cs
@@ -9,7 +9,9 @@ namespace BirthdayBot;
///
/// Loads and holds configuration values.
///
-class Configuration {
+partial class Configuration {
+ [GeneratedRegex(@"(?\d{1,2})[-,](?\d{1,2})")]
+ private static partial Regex ShardRangeParser();
const string KeyShardRange = "ShardRange";
public string BotToken { get; }
@@ -18,7 +20,7 @@ class Configuration {
public int ShardStart { get; }
public int ShardAmount { get; }
public int ShardTotal { get; }
-
+
public string? SqlHost { get; }
public string? SqlDatabase { get; }
public string SqlUsername { get; }
@@ -70,8 +72,7 @@ class Configuration {
var shardRangeInput = args.ShardRange ?? ReadConfKey(jc, KeyShardRange, false);
if (!string.IsNullOrWhiteSpace(shardRangeInput)) {
- Regex srPicker = new(@"(?\d{1,2})[-,]{1}(?\d{1,2})");
- var m = srPicker.Match(shardRangeInput);
+ var m = ShardRangeParser().Match(shardRangeInput);
if (m.Success) {
ShardStart = int.Parse(m.Groups["low"].Value);
var high = int.Parse(m.Groups["high"].Value);
diff --git a/Data/Extensions.cs b/Data/Extensions.cs
index 01ad926..8693014 100644
--- a/Data/Extensions.cs
+++ b/Data/Extensions.cs
@@ -5,7 +5,7 @@ internal static class Extensions {
/// If it doesn't exist in the database, returns true.
///
public static GuildConfig GetConfigOrNew(this SocketGuild guild, BotDatabaseContext db)
- => db.GuildConfigurations.Where(g => g.GuildId == guild.Id).FirstOrDefault()
+ => db.GuildConfigurations.Where(g => g.GuildId == guild.Id).FirstOrDefault()
?? new GuildConfig() { IsNew = true, GuildId = guild.Id };
///
diff --git a/Data/GuildConfig.cs b/Data/GuildConfig.cs
index a4cfb1d..09569cd 100644
--- a/Data/GuildConfig.cs
+++ b/Data/GuildConfig.cs
@@ -21,7 +21,7 @@ public class GuildConfig {
public string? AnnounceMessagePl { get; set; }
public bool AnnouncePing { get; set; }
-
+
public DateTimeOffset LastSeen { get; set; }
[InverseProperty(nameof(UserEntry.Guild))]
diff --git a/Readme.md b/Readme.md
index 423731f..08f7847 100644
--- a/Readme.md
+++ b/Readme.md
@@ -9,9 +9,8 @@ An automated way to recognize birthdays in your community!
#### Running your own instance
You need:
-* .NET 6 (https://dotnet.microsoft.com/en-us/)
+* .NET 8 (https://dotnet.microsoft.com/en-us/)
* PostgreSQL (https://www.postgresql.org/)
-* EF Core tools (https://learn.microsoft.com/en-us/ef/core/get-started/overview/install#get-the-entity-framework-core-tools)
* A Discord bot token (https://discord.com/developers/applications)
Get your bot token and set up your database user and schema, then create a JSON file containing the following:
@@ -28,10 +27,11 @@ Get your bot token and set up your database user and schema, then create a JSON
Then run the following commands:
```sh
$ dotnet restore
+$ dotnet tool restore
$ dotnet ef database update -- -c path/to/config.json
```
And finally, to run the bot:
-```
+```sh
$ dotnet run -c Release -- -c path/to/config.json
```
\ No newline at end of file
diff --git a/ShardManager.cs b/ShardManager.cs
index 5cf4612..f6211df 100644
--- a/ShardManager.cs
+++ b/ShardManager.cs
@@ -36,7 +36,7 @@ class ShardManager : IDisposable {
Config = cfg;
// Allocate shards based on configuration
- _shards = new Dictionary();
+ _shards = [];
for (var i = Config.ShardStart; i < (Config.ShardStart + Config.ShardAmount); i++) {
_shards.Add(i, null);
}
@@ -128,7 +128,7 @@ class ShardManager : IDisposable {
} else {
shardStatuses.Append('.');
}
-
+
shardStatuses.AppendLine();
if (lastRun > DeadShardThreshold) {