Change build settings, fix minor problems

This commit is contained in:
Noi 2022-05-25 19:27:53 -07:00
parent e1a39f964f
commit 94d4a27e85
13 changed files with 163 additions and 91 deletions

View file

@ -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

1
.gitignore vendored
View file

@ -2,3 +2,4 @@
[Oo]bj/
.vs/
*.user
output/

View file

@ -3,15 +3,16 @@
/// <summary>
/// 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.
/// </summary>
class RateLimit<T> {
class RateLimit<T> where T : notnull {
public const ushort DefaultTimeout = 20; // Skeeter's a cool guy and you can't convince me otherwise.
/// <summary>
/// Time until an entry within this instance expires, in seconds.
/// </summary>
public uint Timeout { get; }
#pragma warning disable CS8714
private Dictionary<T, DateTime> Entries { get; } = new Dictionary<T, DateTime>();
#pragma warning restore CS8714
public RateLimit() : this(DefaultTimeout) { }
public RateLimit(uint timeout) => Timeout = timeout;

View file

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
@ -7,6 +7,9 @@
<Nullable>enable</Nullable>
<Authors>NoiTheCat</Authors>
<Description>A set of standard modules for use with RegexBot.</Description>
<BaseOutputPath>$(SolutionDir)\output</BaseOutputPath>
<UseCommonOutputDirectory>true</UseCommonOutputDirectory>
<DebugType>embedded</DebugType>
</PropertyGroup>
<ItemGroup>

View file

@ -25,13 +25,9 @@ public class FilterList {
/// <exception cref="FormatException">
/// Thrown if there is a problem with input. The exception message specifies the reason.
/// </exception>
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);
}

View file

@ -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) {

View file

@ -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 {
/// </summary>
internal string[] Assemblies { get; }
/// <summary>
/// Connection string for accessing the PostgreSQL database.
/// </summary>
internal string PostgresConnString { get; }
/// <summary>
/// Webhook URL for bot log reporting.
/// </summary>
@ -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<string>()!;
if (string.IsNullOrEmpty(PostgresConnString))
throw new Exception($"'{nameof(PostgresConnString)}' is not properly specified in configuration.");
var pginput = conf[nameof(BotDatabaseContext.PostgresConnectionString)]?.Value<string>()!;
if (string.IsNullOrEmpty(pginput))
throw new Exception($"'{nameof(BotDatabaseContext.PostgresConnectionString)}' is not properly specified in configuration.");
BotDatabaseContext.PostgresConnectionString = pginput;
InstanceLogTarget = conf[nameof(InstanceLogTarget)]?.Value<string>()!;
if (string.IsNullOrEmpty(InstanceLogTarget))

View file

@ -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<RegexbotModule>();
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;
}
}

View file

@ -8,11 +8,8 @@
<Version>0.0.1</Version>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<DebugType>none</DebugType>
<DebugSymbols>false</DebugSymbols>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
<BaseOutputPath>$(SolutionDir)\output</BaseOutputPath>
</PropertyGroup>
<ItemGroup>
@ -24,13 +21,15 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="CommandLineParser" Version="2.8.0" />
<PackageReference Include="Discord.Net" Version="3.6.1" />
<PackageReference Include="CommandLineParser" Version="2.9.1" />
<PackageReference Include="Discord.Net" Version="3.7.0" />
<PackageReference Include="EFCore.NamingConventions" Version="6.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.5">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.5" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="Npgsql" Version="6.0.4" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="6.0.4" />

View file

@ -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");
}
}

View file

@ -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
/// <summary>
/// Common processing for kicks and bans. Called by DoKickAsync and DoBanAsync.
/// </summary>
/// <param name="logReason">The reason to insert into the Audit Log.</param>
// Hooked (indirectly)
[System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1822:Mark members as static")]
internal async Task<BanKickResult> BanOrKickAsync(
RemovalType t, SocketGuild guild, string source, ulong target, int banPurgeDays,
string logReason, bool sendDmToTarget) {
internal async Task<BanKickResult> 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);
}

View file

@ -5,8 +5,6 @@ namespace RegexBot;
partial class RegexbotClient {
private readonly CommonFunctionsService _svcCommonFunctions;
public enum RemovalType { None, Ban, Kick }
/// <summary>
/// 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 {
/// <param name="purgeDays">Number of days of prior post history to delete on ban. Must be between 0-7.</param>
/// <param name="reason">Reason for the action. Sent to the Audit Log and user (if specified).</param>
/// <param name="sendDMToTarget">Specify whether to send a direct message to the target user informing them of the action.</param>
public Task<BanKickResult> BanAsync(SocketGuild guild, string source, ulong targetUser,
int purgeDays, string reason, bool sendDMToTarget)
public Task<BanKickResult> BanAsync(SocketGuild guild,
string source,
ulong targetUser,
int purgeDays,
string? reason,
bool sendDMToTarget)
=> _svcCommonFunctions.BanOrKickAsync(RemovalType.Ban, guild, source, targetUser, purgeDays, reason, sendDMToTarget);
/// <summary>
@ -28,8 +30,12 @@ partial class RegexbotClient {
/// EntityCache lookup to determine the target.
/// </summary>
/// <param name="targetSearch">The EntityCache search string.</param>
public async Task<BanKickResult> BanAsync(SocketGuild guild, string source, string targetSearch,
int purgeDays, string reason, bool sendDMToTarget) {
public async Task<BanKickResult> 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).
/// </param>
public Task<BanKickResult> KickAsync(SocketGuild guild, string source, ulong targetUser, string reason, bool sendDMToTarget)
=> _svcCommonFunctions.BanOrKickAsync(RemovalType.Ban, guild, source, targetUser, 0, reason, sendDMToTarget);
public Task<BanKickResult> KickAsync(SocketGuild guild,
string source,
ulong targetUser,
string? reason,
bool sendDMToTarget)
=> _svcCommonFunctions.BanOrKickAsync(RemovalType.Kick, guild, source, targetUser, default, reason, sendDMToTarget);
/// <summary>
/// Similar to <see cref="KickAsync(SocketGuild, string, ulong, string, bool)"/>, but making use of an
/// EntityCache lookup to determine the target.
/// </summary>
/// <param name="targetSearch">The EntityCache search string.</param>
public async Task<BanKickResult> KickAsync(SocketGuild guild, string source, string targetSearch, string reason, bool sendDMToTarget) {
public async Task<BanKickResult> 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);

View file

@ -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;
/// <summary>
/// Specifies possible outcomes for the removal of a user from a guild.
/// </summary>
public enum RemovalType {
/// <summary>
/// Default value. Not used in any actual circumstances.
/// </summary>
None,
/// <summary>
/// Specifies that the type of removal includes placing the user on the guild's ban list.
/// </summary>
Ban,
/// <summary>
/// Specifies that the user is removed from the server via kick.
/// </summary>
Kick
}