diff --git a/.editorconfig b/.editorconfig
index cde0129..0b8c1b3 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -75,59 +75,59 @@ dotnet_style_allow_statement_immediately_after_block_experimental = true
#### C# Coding Conventions ####
# var preferences
-csharp_style_var_elsewhere = false
-csharp_style_var_for_built_in_types = false
-csharp_style_var_when_type_is_apparent = false
+csharp_style_var_elsewhere = false:silent
+csharp_style_var_for_built_in_types = true:suggestion
+csharp_style_var_when_type_is_apparent = false:silent
# Expression-bodied members
-csharp_style_expression_bodied_accessors = true
-csharp_style_expression_bodied_constructors = true
-csharp_style_expression_bodied_indexers = true
-csharp_style_expression_bodied_lambdas = true
-csharp_style_expression_bodied_local_functions = true
-csharp_style_expression_bodied_methods = true
-csharp_style_expression_bodied_operators = true
-csharp_style_expression_bodied_properties = true
+csharp_style_expression_bodied_accessors = true:silent
+csharp_style_expression_bodied_constructors = true:silent
+csharp_style_expression_bodied_indexers = true:silent
+csharp_style_expression_bodied_lambdas = true:silent
+csharp_style_expression_bodied_local_functions = true:silent
+csharp_style_expression_bodied_methods = true:silent
+csharp_style_expression_bodied_operators = true:silent
+csharp_style_expression_bodied_properties = true:silent
# Pattern matching preferences
-csharp_style_pattern_matching_over_as_with_null_check = true
-csharp_style_pattern_matching_over_is_with_cast_check = true
-csharp_style_prefer_not_pattern = true
+csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
+csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
+csharp_style_prefer_not_pattern = true:suggestion
csharp_style_prefer_pattern_matching = true:suggestion
-csharp_style_prefer_switch_expression = true
+csharp_style_prefer_switch_expression = true:suggestion
# Null-checking preferences
-csharp_style_conditional_delegate_call = true
+csharp_style_conditional_delegate_call = true:suggestion
# Modifier preferences
-csharp_prefer_static_local_function = true
+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
-csharp_prefer_simple_using_statement = true
-csharp_style_namespace_declarations = file_scoped
+csharp_prefer_braces = when_multiline:silent
+csharp_prefer_simple_using_statement = true:suggestion
+csharp_style_namespace_declarations = file_scoped:suggestion
# Expression-level preferences
-csharp_prefer_simple_default_expression = true
-csharp_style_deconstructed_variable_declaration = true
-csharp_style_implicit_object_creation_when_type_is_apparent = true
-csharp_style_inlined_variable_declaration = true
+csharp_prefer_simple_default_expression = true:suggestion
+csharp_style_deconstructed_variable_declaration = true:suggestion
+csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion
+csharp_style_inlined_variable_declaration = true:suggestion
csharp_style_pattern_local_over_anonymous_function = true
-csharp_style_prefer_index_operator = true
-csharp_style_prefer_null_check_over_type_check = true
-csharp_style_prefer_range_operator = true
-csharp_style_throw_expression = true
-csharp_style_unused_value_assignment_preference = discard_variable
-csharp_style_unused_value_expression_statement_preference = discard_variable
+csharp_style_prefer_index_operator = true:suggestion
+csharp_style_prefer_null_check_over_type_check = true:suggestion
+csharp_style_prefer_range_operator = true:suggestion
+csharp_style_throw_expression = true:suggestion
+csharp_style_unused_value_assignment_preference = discard_variable:suggestion
+csharp_style_unused_value_expression_statement_preference = discard_variable:silent
# 'using' directive preferences
-csharp_using_directive_placement = outside_namespace
+csharp_using_directive_placement = outside_namespace:silent
# New line preferences
-csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = false
-csharp_style_allow_blank_lines_between_consecutive_braces_experimental = false
-csharp_style_allow_embedded_statements_on_same_line_experimental = true
+csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = false:suggestion
+csharp_style_allow_blank_lines_between_consecutive_braces_experimental = false:suggestion
+csharp_style_allow_embedded_statements_on_same_line_experimental = true:silent
#### C# Formatting Rules ####
@@ -217,3 +217,42 @@ dotnet_naming_style.begins_with_i.required_prefix = I
dotnet_naming_style.begins_with_i.required_suffix =
dotnet_naming_style.begins_with_i.word_separator =
dotnet_naming_style.begins_with_i.capitalization = pascal_case
+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
diff --git a/.gitignore b/.gitignore
index 65261fc..bd521b3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,4 @@
[Oo]bj/
.vs/
*.user
+output/
\ No newline at end of file
diff --git a/RegexBot-Modules/RateLimit.cs b/RegexBot-Modules/RateLimit.cs
index 373a2eb..8657a1b 100644
--- a/RegexBot-Modules/RateLimit.cs
+++ b/RegexBot-Modules/RateLimit.cs
@@ -3,15 +3,16 @@
///
/// Helper class for managing rate limit data.
/// More accurately, this class holds entries, not allowing the same entry to be held more than once until a specified
-/// amount of time has paspsed since the entry was originally tracked; useful for a rate limit system.
+/// amount of time has passed since the entry was originally tracked; useful for a rate limit system.
///
-class RateLimit {
+class RateLimit where T : notnull {
public const ushort DefaultTimeout = 20; // Skeeter's a cool guy and you can't convince me otherwise.
+ ///
+ /// Time until an entry within this instance expires, in seconds.
+ ///
public uint Timeout { get; }
-#pragma warning disable CS8714
private Dictionary Entries { get; } = new Dictionary();
-#pragma warning restore CS8714
public RateLimit() : this(DefaultTimeout) { }
public RateLimit(uint timeout) => Timeout = timeout;
diff --git a/RegexBot-Modules/RegexBot-Modules.csproj b/RegexBot-Modules/RegexBot-Modules.csproj
index a3b6ab8..50200f0 100644
--- a/RegexBot-Modules/RegexBot-Modules.csproj
+++ b/RegexBot-Modules/RegexBot-Modules.csproj
@@ -1,4 +1,4 @@
-
+
net6.0
@@ -7,6 +7,9 @@
enable
NoiTheCat
A set of standard modules for use with RegexBot.
+ $(SolutionDir)\output
+ true
+ embedded
diff --git a/RegexBot/Common/FilterList.cs b/RegexBot/Common/FilterList.cs
index 6c7aca2..87135d0 100644
--- a/RegexBot/Common/FilterList.cs
+++ b/RegexBot/Common/FilterList.cs
@@ -25,13 +25,9 @@ public class FilterList {
///
/// Thrown if there is a problem with input. The exception message specifies the reason.
///
- public FilterList(
- JObject config,
- string whitelistKey = "whitelist",
- string blacklistKey = "blacklist",
- string exemptKey = "exempt") {
- if ((whitelistKey != null && config[whitelistKey] != null) &&
- (blacklistKey != null && config[blacklistKey] != null)) {
+ public FilterList(JObject config, string whitelistKey = "whitelist", string blacklistKey = "blacklist", string exemptKey = "exempt") {
+ if (whitelistKey != null && config[whitelistKey] != null &&
+ blacklistKey != null && config[blacklistKey] != null) {
throw new FormatException($"Cannot have both '{whitelistKey}' and '{blacklistKey}' defined at once.");
}
@@ -83,12 +79,12 @@ public class FilterList {
public bool IsFiltered(SocketMessage msg, bool keepId) {
if (Mode == FilterMode.None) return false;
- bool inFilter = FilteredList.IsListMatch(msg, keepId);
+ var isInFilter = FilteredList.IsListMatch(msg, keepId);
if (Mode == FilterMode.Whitelist) {
- if (!inFilter) return true;
+ if (!isInFilter) return true;
return FilterExemptions.IsListMatch(msg, keepId);
} else if (Mode == FilterMode.Blacklist) {
- if (!inFilter) return false;
+ if (!isInFilter) return false;
return !FilterExemptions.IsListMatch(msg, keepId);
}
diff --git a/RegexBot/Data/BotDatabaseContext.cs b/RegexBot/Data/BotDatabaseContext.cs
index 347bbfe..40bb2c5 100644
--- a/RegexBot/Data/BotDatabaseContext.cs
+++ b/RegexBot/Data/BotDatabaseContext.cs
@@ -4,7 +4,7 @@ namespace RegexBot.Data;
public class BotDatabaseContext : DbContext {
private static string? _npgsqlConnectionString;
- internal static string NpgsqlConnectionString {
+ internal static string PostgresConnectionString {
#if DEBUG
get {
if (_npgsqlConnectionString != null) return _npgsqlConnectionString;
@@ -24,7 +24,7 @@ public class BotDatabaseContext : DbContext {
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
- .UseNpgsql(NpgsqlConnectionString)
+ .UseNpgsql(PostgresConnectionString)
.UseSnakeCaseNamingConvention();
protected override void OnModelCreating(ModelBuilder modelBuilder) {
diff --git a/RegexBot/InstanceConfig.cs b/RegexBot/InstanceConfig.cs
index 051f904..e5a4f70 100644
--- a/RegexBot/InstanceConfig.cs
+++ b/RegexBot/InstanceConfig.cs
@@ -1,5 +1,6 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
+using RegexBot.Data;
using System.Reflection;
namespace RegexBot;
@@ -19,11 +20,6 @@ class InstanceConfig {
///
internal string[] Assemblies { get; }
- ///
- /// Connection string for accessing the PostgreSQL database.
- ///
- internal string PostgresConnString { get; }
-
///
/// Webhook URL for bot log reporting.
///
@@ -37,7 +33,7 @@ class InstanceConfig {
internal InstanceConfig(string[] cmdline) {
var opts = Options.ParseOptions(cmdline);
- string path = opts.ConfigFile;
+ var path = opts.ConfigFile;
if (path == null) { // default: config.json in working directory
path = Path.GetDirectoryName(Assembly.GetEntryAssembly()!.Location)
+ "." + Path.DirectorySeparatorChar + "config.json";
@@ -60,9 +56,10 @@ class InstanceConfig {
if (string.IsNullOrEmpty(BotToken))
throw new Exception($"'{nameof(BotToken)}' is not properly specified in configuration.");
- PostgresConnString = conf[nameof(PostgresConnString)]?.Value()!;
- if (string.IsNullOrEmpty(PostgresConnString))
- throw new Exception($"'{nameof(PostgresConnString)}' is not properly specified in configuration.");
+ var pginput = conf[nameof(BotDatabaseContext.PostgresConnectionString)]?.Value()!;
+ if (string.IsNullOrEmpty(pginput))
+ throw new Exception($"'{nameof(BotDatabaseContext.PostgresConnectionString)}' is not properly specified in configuration.");
+ BotDatabaseContext.PostgresConnectionString = pginput;
InstanceLogTarget = conf[nameof(InstanceLogTarget)]?.Value()!;
if (string.IsNullOrEmpty(InstanceLogTarget))
diff --git a/RegexBot/ModuleLoader.cs b/RegexBot/ModuleLoader.cs
index 43bda08..03e59ea 100644
--- a/RegexBot/ModuleLoader.cs
+++ b/RegexBot/ModuleLoader.cs
@@ -1,5 +1,6 @@
using System.Collections.ObjectModel;
using System.Reflection;
+using System.Text;
namespace RegexBot;
@@ -42,12 +43,14 @@ static class ModuleLoader {
select type;
k._svcLogging.DoLog(false, nameof(ModuleLoader), $"Scanning {asm.GetName().Name}");
+ var newreport = new StringBuilder("---> Found module(s):");
var newmods = new List();
foreach (var t in eligibleTypes) {
var mod = Activator.CreateInstance(t, k)!;
- k._svcLogging.DoLog(false, nameof(ModuleLoader), $"---> Loading module {t.FullName}");
+ newreport.Append($" {t.Name}");
newmods.Add((RegexbotModule)mod);
}
+ k._svcLogging.DoLog(false, nameof(ModuleLoader), newreport.ToString());
return newmods;
}
}
diff --git a/RegexBot/RegexBot.csproj b/RegexBot/RegexBot.csproj
index 1da133a..a4a35bc 100644
--- a/RegexBot/RegexBot.csproj
+++ b/RegexBot/RegexBot.csproj
@@ -8,11 +8,8 @@
0.0.1
enable
enable
-
-
-
- none
- false
+ True
+ $(SolutionDir)\output
@@ -24,13 +21,15 @@
-
-
+
+
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
+
diff --git a/RegexBot/RegexbotClient.cs b/RegexBot/RegexbotClient.cs
index fc66b1f..b5c1a76 100644
--- a/RegexBot/RegexbotClient.cs
+++ b/RegexBot/RegexbotClient.cs
@@ -32,11 +32,10 @@ public partial class RegexbotClient {
_svcCommonFunctions = new Services.CommonFunctions.CommonFunctionsService(this);
_svcEntityCache = new Services.EntityCache.EntityCacheService(this);
+ var ver = Assembly.GetExecutingAssembly().GetName().Version!;
+ _svcLogging.DoLog(true, nameof(RegexBot), $"{nameof(RegexBot)} v{ver:3} - https://github.com/NoiTheCat/RegexBot");
+
// Load externally defined functionality
Modules = ModuleLoader.Load(Config, this);
-
- // Everything's ready to go. Print the welcome message here.
- var ver = Assembly.GetExecutingAssembly().GetName().Version!.ToString(3);
- _svcLogging.DoLog(true, nameof(RegexBot), $"{nameof(RegexBot)} v{ver} - https://github.com/NoiTheCat/RegexBot");
}
}
diff --git a/RegexBot/Services/CommonFunctions/CommonFunctionsService.cs b/RegexBot/Services/CommonFunctions/CommonFunctionsService.cs
index 1f5c5a5..18d48cc 100644
--- a/RegexBot/Services/CommonFunctions/CommonFunctionsService.cs
+++ b/RegexBot/Services/CommonFunctions/CommonFunctionsService.cs
@@ -1,6 +1,5 @@
using Discord.Net;
using Discord.WebSocket;
-using static RegexBot.RegexbotClient;
namespace RegexBot.Services.CommonFunctions;
@@ -16,14 +15,15 @@ internal class CommonFunctionsService : Service {
public CommonFunctionsService(RegexbotClient bot) : base(bot) { }
#region Guild member removal
- ///
- /// Common processing for kicks and bans. Called by DoKickAsync and DoBanAsync.
- ///
- /// The reason to insert into the Audit Log.
+ // Hooked (indirectly)
[System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1822:Mark members as static")]
- internal async Task BanOrKickAsync(
- RemovalType t, SocketGuild guild, string source, ulong target, int banPurgeDays,
- string logReason, bool sendDmToTarget) {
+ internal async Task BanOrKickAsync(RemovalType t,
+ SocketGuild guild,
+ string source,
+ ulong target,
+ int banPurgeDays,
+ string? logReason,
+ bool sendDmToTarget) {
if (t == RemovalType.None) throw new ArgumentException("Removal type must be 'ban' or 'kick'.");
if (string.IsNullOrWhiteSpace(logReason)) logReason = "Reason not specified.";
var dmSuccess = true;
@@ -42,9 +42,9 @@ internal class CommonFunctionsService : Service {
// Perform the action
try {
- if (t == RemovalType.Ban) await guild.AddBanAsync(target, banPurgeDays);
+ if (t == RemovalType.Ban) await guild.AddBanAsync(target, banPurgeDays, logReason);
else await utarget!.KickAsync(logReason);
- // TODO !! Insert ban reason! For kick also: Figure out a way to specify invoker.
+ // TODO for kick: Figure out a way to specify invoker.
} catch (HttpException ex) {
return new BanKickResult(ex, dmSuccess, false, t, target);
}
diff --git a/RegexBot/Services/CommonFunctions/Hooks.cs b/RegexBot/Services/CommonFunctions/Hooks.cs
index b531c58..277f241 100644
--- a/RegexBot/Services/CommonFunctions/Hooks.cs
+++ b/RegexBot/Services/CommonFunctions/Hooks.cs
@@ -5,8 +5,6 @@ namespace RegexBot;
partial class RegexbotClient {
private readonly CommonFunctionsService _svcCommonFunctions;
- public enum RemovalType { None, Ban, Kick }
-
///
/// Attempts to ban the given user from the specified guild. It is greatly preferred to call this method
/// instead of manually executing the equivalent method found in Discord.Net. It notifies other services
@@ -19,8 +17,12 @@ partial class RegexbotClient {
/// Number of days of prior post history to delete on ban. Must be between 0-7.
/// Reason for the action. Sent to the Audit Log and user (if specified).
/// Specify whether to send a direct message to the target user informing them of the action.
- public Task BanAsync(SocketGuild guild, string source, ulong targetUser,
- int purgeDays, string reason, bool sendDMToTarget)
+ public Task BanAsync(SocketGuild guild,
+ string source,
+ ulong targetUser,
+ int purgeDays,
+ string? reason,
+ bool sendDMToTarget)
=> _svcCommonFunctions.BanOrKickAsync(RemovalType.Ban, guild, source, targetUser, purgeDays, reason, sendDMToTarget);
///
@@ -28,8 +30,12 @@ partial class RegexbotClient {
/// EntityCache lookup to determine the target.
///
/// The EntityCache search string.
- public async Task BanAsync(SocketGuild guild, string source, string targetSearch,
- int purgeDays, string reason, bool sendDMToTarget) {
+ public async Task BanAsync(SocketGuild guild,
+ string source,
+ string targetSearch,
+ int purgeDays,
+ string? reason,
+ bool sendDMToTarget) {
var result = EcQueryGuildUser(guild.Id, targetSearch);
if (result == null) return new BanKickResult(null, false, true, RemovalType.Ban, 0);
return await BanAsync(guild, source, (ulong)result.UserId, purgeDays, reason, sendDMToTarget);
@@ -52,15 +58,23 @@ partial class RegexbotClient {
/// Specify whether to send a direct message to the target user informing them of the action
/// (that is, a ban/kick message).
///
- public Task KickAsync(SocketGuild guild, string source, ulong targetUser, string reason, bool sendDMToTarget)
- => _svcCommonFunctions.BanOrKickAsync(RemovalType.Ban, guild, source, targetUser, 0, reason, sendDMToTarget);
+ public Task KickAsync(SocketGuild guild,
+ string source,
+ ulong targetUser,
+ string? reason,
+ bool sendDMToTarget)
+ => _svcCommonFunctions.BanOrKickAsync(RemovalType.Kick, guild, source, targetUser, default, reason, sendDMToTarget);
///
/// Similar to , but making use of an
/// EntityCache lookup to determine the target.
///
/// The EntityCache search string.
- public async Task KickAsync(SocketGuild guild, string source, string targetSearch, string reason, bool sendDMToTarget) {
+ public async Task KickAsync(SocketGuild guild,
+ string source,
+ string targetSearch,
+ string? reason,
+ bool sendDMToTarget) {
var result = EcQueryGuildUser(guild.Id, targetSearch);
if (result == null) return new BanKickResult(null, false, true, RemovalType.Kick, 0);
return await KickAsync(guild, source, (ulong)result.UserId, reason, sendDMToTarget);
diff --git a/RegexBot/Services/CommonFunctions/RemovalType.cs b/RegexBot/Services/CommonFunctions/RemovalType.cs
new file mode 100644
index 0000000..2a2ee86
--- /dev/null
+++ b/RegexBot/Services/CommonFunctions/RemovalType.cs
@@ -0,0 +1,20 @@
+// Despite specific to CommonFunctionsService, this enum is meant to be visible by modules too,
+// thus it is placed within the root namespace.
+namespace RegexBot;
+///
+/// Specifies possible outcomes for the removal of a user from a guild.
+///
+public enum RemovalType {
+ ///
+ /// Default value. Not used in any actual circumstances.
+ ///
+ None,
+ ///
+ /// Specifies that the type of removal includes placing the user on the guild's ban list.
+ ///
+ Ban,
+ ///
+ /// Specifies that the user is removed from the server via kick.
+ ///
+ Kick
+}