commit
0c24c36da2
33 changed files with 119 additions and 154 deletions
12
.config/dotnet-tools.json
Normal file
12
.config/dotnet-tools.json
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"isRoot": true,
|
||||||
|
"tools": {
|
||||||
|
"dotnet-ef": {
|
||||||
|
"version": "8.0.6",
|
||||||
|
"commands": [
|
||||||
|
"dotnet-ef"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,13 +13,13 @@ tab_width = 4
|
||||||
|
|
||||||
# New line preferences
|
# New line preferences
|
||||||
end_of_line = crlf
|
end_of_line = crlf
|
||||||
insert_final_newline = false
|
insert_final_newline = true
|
||||||
|
|
||||||
#### .NET Coding Conventions ####
|
#### .NET Coding Conventions ####
|
||||||
|
|
||||||
# Organize usings
|
# Organize usings
|
||||||
dotnet_separate_import_directive_groups = false
|
dotnet_separate_import_directive_groups = false
|
||||||
dotnet_sort_system_directives_first = false
|
dotnet_sort_system_directives_first = true
|
||||||
file_header_template = unset
|
file_header_template = unset
|
||||||
|
|
||||||
# this. and Me. preferences
|
# this. and Me. preferences
|
||||||
|
@ -63,7 +63,7 @@ dotnet_style_prefer_simplified_interpolation = true
|
||||||
dotnet_style_readonly_field = true
|
dotnet_style_readonly_field = true
|
||||||
|
|
||||||
# Parameter preferences
|
# Parameter preferences
|
||||||
dotnet_code_quality_unused_parameters = all
|
dotnet_code_quality_unused_parameters = true
|
||||||
|
|
||||||
# Suppression preferences
|
# Suppression preferences
|
||||||
dotnet_remove_unnecessary_suppression_exclusions = none
|
dotnet_remove_unnecessary_suppression_exclusions = none
|
||||||
|
@ -77,7 +77,7 @@ dotnet_style_allow_statement_immediately_after_block_experimental = true
|
||||||
# var preferences
|
# var preferences
|
||||||
csharp_style_var_elsewhere = false:silent
|
csharp_style_var_elsewhere = false:silent
|
||||||
csharp_style_var_for_built_in_types = true:suggestion
|
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
|
# Expression-bodied members
|
||||||
csharp_style_expression_bodied_accessors = true:silent
|
csharp_style_expression_bodied_accessors = true:silent
|
||||||
|
@ -101,7 +101,6 @@ csharp_style_conditional_delegate_call = true:suggestion
|
||||||
|
|
||||||
# Modifier preferences
|
# Modifier preferences
|
||||||
csharp_prefer_static_local_function = true:suggestion
|
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
|
# Code-block preferences
|
||||||
csharp_prefer_braces = when_multiline:silent
|
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_tuple_swap = true:suggestion
|
||||||
csharp_style_prefer_extended_property_pattern = true:suggestion
|
csharp_style_prefer_extended_property_pattern = true:suggestion
|
||||||
|
|
||||||
[*.{cs,vb}]
|
csharp_style_prefer_primary_constructors = true:suggestion
|
||||||
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
|
|
4
.vscode/launch.json
vendored
4
.vscode/launch.json
vendored
|
@ -10,8 +10,8 @@
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"preLaunchTask": "build",
|
"preLaunchTask": "build",
|
||||||
// If you have changed target frameworks, make sure to update the program path.
|
// If you have changed target frameworks, make sure to update the program path.
|
||||||
"program": "${workspaceFolder}/bin/Debug/net6.0/RegexBot.dll",
|
"program": "${workspaceFolder}/bin/Debug/net8.0/RegexBot.dll",
|
||||||
"args": [ "-c", "${workspaceFolder}/bin/Debug/net6.0/config.json" ],
|
"args": [ "-c", "${workspaceFolder}/bin/Debug/config.json" ],
|
||||||
"cwd": "${workspaceFolder}",
|
"cwd": "${workspaceFolder}",
|
||||||
// For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console
|
// For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console
|
||||||
"console": "internalConsole",
|
"console": "internalConsole",
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
|
||||||
namespace RegexBot.Common;
|
namespace RegexBot.Common;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a commonly-used configuration structure: an array of strings consisting of <see cref="EntityName"/> values.
|
/// Represents a commonly-used configuration structure: an array of strings consisting of <see cref="EntityName"/> values.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class EntityList : IEnumerable<EntityName> {
|
public class EntityList : IEnumerable<EntityName> {
|
||||||
private readonly IReadOnlyCollection<EntityName> _innerList;
|
private readonly ReadOnlyCollection<EntityName> _innerList;
|
||||||
|
|
||||||
/// <summary>Gets an enumerable collection of all role names defined in this list.</summary>
|
/// <summary>Gets an enumerable collection of all role names defined in this list.</summary>
|
||||||
public IEnumerable<EntityName> Roles
|
public IEnumerable<EntityName> Roles
|
||||||
|
|
|
@ -11,7 +11,7 @@ public class RateLimit<T> where T : notnull {
|
||||||
/// Time until an entry within this instance expires, in seconds.
|
/// Time until an entry within this instance expires, in seconds.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int Timeout { get; }
|
public int Timeout { get; }
|
||||||
private Dictionary<T, DateTime> Entries { get; } = new Dictionary<T, DateTime>();
|
private Dictionary<T, DateTime> Entries { get; } = [];
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new <see cref="RateLimit<T>"/> instance with the default timeout value.
|
/// Creates a new <see cref="RateLimit<T>"/> instance with the default timeout value.
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
using Discord;
|
using Discord;
|
||||||
using RegexBot.Data;
|
using RegexBot.Data;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Net;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
|
@ -9,26 +8,30 @@ namespace RegexBot.Common;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Miscellaneous utility methods useful for the bot and modules.
|
/// Miscellaneous utility methods useful for the bot and modules.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static class Utilities {
|
public static partial class Utilities {
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a compiled regex that matches a channel tag and pulls its snowflake value.
|
/// Gets a precompiled regex that matches a channel tag and pulls its snowflake value.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static Regex ChannelMention { get; } = new(@"<#(?<snowflake>\d+)>", RegexOptions.Compiled);
|
[GeneratedRegex(@"<#(?<snowflake>\d+)>")]
|
||||||
|
public static partial Regex ChannelMentionRegex();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a compiled regex that matches a custom emoji and pulls its name and ID.
|
/// Gets a precompiled regex that matches a custom emoji and pulls its name and ID.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static Regex CustomEmoji { get; } = new(@"<:(?<name>[A-Za-z0-9_]{2,}):(?<ID>\d+)>", RegexOptions.Compiled);
|
[GeneratedRegex(@"<:(?<name>[A-Za-z0-9_]{2,}):(?<ID>\d+)>")]
|
||||||
|
public static partial Regex CustomEmojiRegex();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a compiled regex that matches a fully formed Discord handle, extracting the name and discriminator.
|
/// Gets a precompiled regex that matches a fully formed Discord handle, extracting the name and discriminator.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static Regex DiscriminatorSearch { get; } = new(@"(.+)#(\d{4}(?!\d))", RegexOptions.Compiled);
|
[GeneratedRegex(@"(.+)#(\d{4}(?!\d))")]
|
||||||
|
public static partial Regex DiscriminatorSearchRegex();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a compiled regex that matches a user tag and pulls its snowflake value.
|
/// Gets a precompiled regex that matches a user tag and pulls its snowflake value.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static Regex UserMention { get; } = new(@"<@!?(?<snowflake>\d+)>", RegexOptions.Compiled);
|
[GeneratedRegex(@"<@!?(?<snowflake>\d+)>")]
|
||||||
|
public static partial Regex UserMentionRegex();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Performs common checks on the specified message to see if it fits all the criteria of a
|
/// Performs common checks on the specified message to see if it fits all the criteria of a
|
||||||
|
@ -77,7 +80,7 @@ public static class Utilities {
|
||||||
/// If given string is in an EntityName format, returns a displayable representation of it based on
|
/// If given string is in an EntityName format, returns a displayable representation of it based on
|
||||||
/// a cache query. Otherwise, returns the input string as-is.
|
/// a cache query. Otherwise, returns the input string as-is.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[return: NotNullIfNotNull("input")]
|
[return: NotNullIfNotNull(nameof(input))]
|
||||||
public static string? TryFromEntityNameString(string? input, RegexbotClient bot) {
|
public static string? TryFromEntityNameString(string? input, RegexbotClient bot) {
|
||||||
string? result = null;
|
string? result = null;
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -51,9 +51,8 @@ class Configuration {
|
||||||
throw new Exception($"'{nameof(Assemblies)}' is not properly specified in configuration.");
|
throw new Exception($"'{nameof(Assemblies)}' is not properly specified in configuration.");
|
||||||
}
|
}
|
||||||
|
|
||||||
var dbconf = conf["DatabaseOptions"]?.Value<JObject>();
|
var dbconf = (conf["DatabaseOptions"]?.Value<JObject>())
|
||||||
if (dbconf == null) throw new Exception("Database settings were not specified in configuration.");
|
?? throw new Exception("Database settings were not specified in configuration.");
|
||||||
// TODO more detailed database configuration? password file, other advanced authentication settings... look into this.
|
|
||||||
Host = ReadConfKey<string>(dbconf, nameof(Host), false);
|
Host = ReadConfKey<string>(dbconf, nameof(Host), false);
|
||||||
Database = ReadConfKey<string?>(dbconf, nameof(Database), false);
|
Database = ReadConfKey<string?>(dbconf, nameof(Database), false);
|
||||||
Username = ReadConfKey<string>(dbconf, nameof(Username), true);
|
Username = ReadConfKey<string>(dbconf, nameof(Username), true);
|
||||||
|
|
|
@ -4,9 +4,4 @@
|
||||||
/// Represents an error occurring when a module attempts to create a new guild state object
|
/// Represents an error occurring when a module attempts to create a new guild state object
|
||||||
/// (that is, read or refresh its configuration).
|
/// (that is, read or refresh its configuration).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ModuleLoadException : Exception {
|
public class ModuleLoadException(string message) : Exception(message) { }
|
||||||
/// <summary>
|
|
||||||
/// Initializes this exception class with the specified error message.
|
|
||||||
/// </summary>
|
|
||||||
public ModuleLoadException(string message) : base(message) { }
|
|
||||||
}
|
|
||||||
|
|
|
@ -38,7 +38,7 @@ static class ModuleLoader {
|
||||||
return modules.AsReadOnly();
|
return modules.AsReadOnly();
|
||||||
}
|
}
|
||||||
|
|
||||||
static IEnumerable<RegexbotModule> LoadModulesFromAssembly(Assembly asm, RegexbotClient rb) {
|
static List<RegexbotModule> LoadModulesFromAssembly(Assembly asm, RegexbotClient rb) {
|
||||||
var eligibleTypes = from type in asm.GetTypes()
|
var eligibleTypes = from type in asm.GetTypes()
|
||||||
where !type.IsAssignableFrom(typeof(RegexbotModule))
|
where !type.IsAssignableFrom(typeof(RegexbotModule))
|
||||||
where type.GetCustomAttribute<RegexbotModuleAttribute>() != null
|
where type.GetCustomAttribute<RegexbotModuleAttribute>() != null
|
||||||
|
|
|
@ -45,7 +45,7 @@ internal class AutoResponder : RegexbotModule {
|
||||||
if (def.Command == null) {
|
if (def.Command == null) {
|
||||||
await msg.Channel.SendMessageAsync(def.GetResponse());
|
await msg.Channel.SendMessageAsync(def.GetResponse());
|
||||||
} else {
|
} else {
|
||||||
var cmdline = def.Command.Split(new char[] { ' ' }, 2);
|
var cmdline = def.Command.Split([' '], 2);
|
||||||
|
|
||||||
var ps = new ProcessStartInfo() {
|
var ps = new ProcessStartInfo() {
|
||||||
FileName = cmdline[0],
|
FileName = cmdline[0],
|
||||||
|
|
|
@ -83,7 +83,7 @@ internal sealed class EntryRole : RegexbotModule, IDisposable {
|
||||||
foreach (var g in DiscordClient.Guilds) {
|
foreach (var g in DiscordClient.Guilds) {
|
||||||
subworkers.Add(RoleApplyGuildSubWorker(g));
|
subworkers.Add(RoleApplyGuildSubWorker(g));
|
||||||
}
|
}
|
||||||
Task.WaitAll(subworkers.ToArray());
|
Task.WaitAll([.. subworkers]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@ class GuildData {
|
||||||
|
|
||||||
const int WaitTimeMax = 600; // 10 minutes
|
const int WaitTimeMax = 600; // 10 minutes
|
||||||
|
|
||||||
public GuildData(JObject conf) : this(conf, new Dictionary<ulong, DateTimeOffset>()) { }
|
public GuildData(JObject conf) : this(conf, []) { }
|
||||||
|
|
||||||
public GuildData(JObject conf, Dictionary<ulong, DateTimeOffset> _waitingList) {
|
public GuildData(JObject conf, Dictionary<ulong, DateTimeOffset> _waitingList) {
|
||||||
WaitingList = _waitingList;
|
WaitingList = _waitingList;
|
||||||
|
|
|
@ -22,9 +22,7 @@ class Ban : BanKick {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Kick : BanKick {
|
class Kick(ModCommands module, JObject config) : BanKick(module, config, false) {
|
||||||
public Kick(ModCommands module, JObject config) : base(module, config, false) { }
|
|
||||||
|
|
||||||
protected override async Task ContinueInvoke(SocketGuild g, SocketMessage msg, string? reason,
|
protected override async Task ContinueInvoke(SocketGuild g, SocketMessage msg, string? reason,
|
||||||
ulong targetId, CachedGuildUser? targetQuery, SocketUser? targetUser) {
|
ulong targetId, CachedGuildUser? targetQuery, SocketUser? targetUser) {
|
||||||
// Kick: Unlike ban, must find the guild user in order to proceed
|
// Kick: Unlike ban, must find the guild user in order to proceed
|
||||||
|
@ -76,7 +74,7 @@ abstract class BanKick : CommandConfig {
|
||||||
|
|
||||||
// Usage: (command) (user) (reason)
|
// Usage: (command) (user) (reason)
|
||||||
public override async Task Invoke(SocketGuild g, SocketMessage msg) {
|
public override async Task Invoke(SocketGuild g, SocketMessage msg) {
|
||||||
var line = msg.Content.Split(new char[] { ' ' }, 3, StringSplitOptions.RemoveEmptyEntries);
|
var line = SplitToParams(msg, 3);
|
||||||
string targetstr;
|
string targetstr;
|
||||||
string? reason;
|
string? reason;
|
||||||
if (line.Length < 2) {
|
if (line.Length < 2) {
|
||||||
|
|
|
@ -3,16 +3,10 @@ using System.Diagnostics;
|
||||||
|
|
||||||
namespace RegexBot.Modules.ModCommands.Commands;
|
namespace RegexBot.Modules.ModCommands.Commands;
|
||||||
[DebuggerDisplay("Command definition '{Label}'")]
|
[DebuggerDisplay("Command definition '{Label}'")]
|
||||||
abstract class CommandConfig {
|
abstract class CommandConfig(ModCommands module, JObject config) {
|
||||||
public string Label { get; }
|
public string Label { get; } = config[nameof(Label)]!.Value<string>()!;
|
||||||
public string Command { get; }
|
public string Command { get; } = config[nameof(Command)]!.Value<string>()!;
|
||||||
protected ModCommands Module { get; }
|
protected ModCommands Module { get; } = module;
|
||||||
|
|
||||||
protected CommandConfig(ModCommands module, JObject config) {
|
|
||||||
Module = module;
|
|
||||||
Label = config[nameof(Label)]!.Value<string>()!;
|
|
||||||
Command = config[nameof(Command)]!.Value<string>()!;
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract Task Invoke(SocketGuild g, SocketMessage msg);
|
public abstract Task Invoke(SocketGuild g, SocketMessage msg);
|
||||||
|
|
||||||
|
@ -20,6 +14,7 @@ abstract class CommandConfig {
|
||||||
protected const string TargetNotFound = ":x: **Unable to find the given user.**";
|
protected const string TargetNotFound = ":x: **Unable to find the given user.**";
|
||||||
|
|
||||||
protected abstract string DefaultUsageMsg { get; }
|
protected abstract string DefaultUsageMsg { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sends out the default usage message (<see cref="DefaultUsageMsg"/>) within an embed.
|
/// Sends out the default usage message (<see cref="DefaultUsageMsg"/>) within an embed.
|
||||||
/// An optional message can be included, for uses such as notifying users of incorrect usage.
|
/// An optional message can be included, for uses such as notifying users of incorrect usage.
|
||||||
|
@ -36,4 +31,15 @@ abstract class CommandConfig {
|
||||||
};
|
};
|
||||||
await target.SendMessageAsync(message ?? "", embed: usageEmbed.Build());
|
await target.SendMessageAsync(message ?? "", embed: usageEmbed.Build());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal static readonly char[] separator = [' '];
|
||||||
|
/// <summary>
|
||||||
|
/// For the given message's content, assumes its message is a command and returns its parameters
|
||||||
|
/// as an array of substrings.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="msg">The incoming message to process.</param>
|
||||||
|
/// <param name="maxParams">The number of parameters to expect.</param>
|
||||||
|
/// <returns>A string array with 0 to maxParams - 1 elements.</returns>
|
||||||
|
protected static string[] SplitToParams(SocketMessage msg, int maxParams)
|
||||||
|
=> msg.Content.Split(separator, maxParams, StringSplitOptions.RemoveEmptyEntries);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,7 @@
|
||||||
namespace RegexBot.Modules.ModCommands.Commands;
|
namespace RegexBot.Modules.ModCommands.Commands;
|
||||||
class ConfReload : CommandConfig {
|
class ConfReload(ModCommands module, JObject config) : CommandConfig(module, config) {
|
||||||
protected override string DefaultUsageMsg => null!;
|
protected override string DefaultUsageMsg => null!;
|
||||||
|
|
||||||
// No configuration.
|
|
||||||
public ConfReload(ModCommands module, JObject config) : base(module, config) { }
|
|
||||||
|
|
||||||
// Usage: (command)
|
// Usage: (command)
|
||||||
public override Task Invoke(SocketGuild g, SocketMessage msg) {
|
public override Task Invoke(SocketGuild g, SocketMessage msg) {
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
|
|
|
@ -2,9 +2,7 @@ using RegexBot.Common;
|
||||||
|
|
||||||
namespace RegexBot.Modules.ModCommands.Commands;
|
namespace RegexBot.Modules.ModCommands.Commands;
|
||||||
// Note and Warn commands are highly similar in implementnation, and thus are handled in a single class.
|
// Note and Warn commands are highly similar in implementnation, and thus are handled in a single class.
|
||||||
class Note : NoteWarn {
|
class Note(ModCommands module, JObject config) : NoteWarn(module, config) {
|
||||||
public Note(ModCommands module, JObject config) : base(module, config) { }
|
|
||||||
|
|
||||||
protected override string DefaultUsageMsg => string.Format(_usageHeader, Command)
|
protected override string DefaultUsageMsg => string.Format(_usageHeader, Command)
|
||||||
+ "Appends a note to the moderation log for the given user.";
|
+ "Appends a note to the moderation log for the given user.";
|
||||||
protected override async Task ContinueInvoke(SocketGuild g, SocketMessage msg, string logMessage, SocketUser targetUser) {
|
protected override async Task ContinueInvoke(SocketGuild g, SocketMessage msg, string logMessage, SocketUser targetUser) {
|
||||||
|
@ -13,9 +11,7 @@ class Note : NoteWarn {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Warn : NoteWarn {
|
class Warn(ModCommands module, JObject config) : NoteWarn(module, config) {
|
||||||
public Warn(ModCommands module, JObject config) : base(module, config) { }
|
|
||||||
|
|
||||||
protected override string DefaultUsageMsg => string.Format(_usageHeader, Command)
|
protected override string DefaultUsageMsg => string.Format(_usageHeader, Command)
|
||||||
+ "Issues a warning to the given user, logging the instance to this bot's moderation log "
|
+ "Issues a warning to the given user, logging the instance to this bot's moderation log "
|
||||||
+ "and notifying the offending user over DM of the issued warning.";
|
+ "and notifying the offending user over DM of the issued warning.";
|
||||||
|
@ -45,7 +41,7 @@ abstract class NoteWarn : CommandConfig {
|
||||||
|
|
||||||
// Usage: (command) (user) (message)
|
// Usage: (command) (user) (message)
|
||||||
public override async Task Invoke(SocketGuild g, SocketMessage msg) {
|
public override async Task Invoke(SocketGuild g, SocketMessage msg) {
|
||||||
var line = msg.Content.Split(new char[] { ' ' }, 3, StringSplitOptions.RemoveEmptyEntries);
|
var line = SplitToParams(msg, 3);
|
||||||
if (line.Length != 3) {
|
if (line.Length != 3) {
|
||||||
await SendUsageMessageAsync(msg.Channel, ":x: Not all required parameters were specified.");
|
await SendUsageMessageAsync(msg.Channel, ":x: Not all required parameters were specified.");
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -1,17 +1,15 @@
|
||||||
using RegexBot.Common;
|
using RegexBot.Common;
|
||||||
|
|
||||||
namespace RegexBot.Modules.ModCommands.Commands;
|
namespace RegexBot.Modules.ModCommands.Commands;
|
||||||
class RoleAdd : RoleManipulation {
|
class RoleAdd(ModCommands module, JObject config) : RoleManipulation(module, config) {
|
||||||
protected override (string, string) String1 => ("Adds", "to");
|
protected override (string, string) String1 => ("Adds", "to");
|
||||||
protected override string String2 => "set";
|
protected override string String2 => "set";
|
||||||
public RoleAdd(ModCommands module, JObject config) : base(module, config) { }
|
|
||||||
protected override async Task ContinueInvoke(SocketGuildUser target, SocketRole role) => await target.AddRoleAsync(role);
|
protected override async Task ContinueInvoke(SocketGuildUser target, SocketRole role) => await target.AddRoleAsync(role);
|
||||||
}
|
}
|
||||||
|
|
||||||
class RoleDel : RoleManipulation {
|
class RoleDel(ModCommands module, JObject config) : RoleManipulation(module, config) {
|
||||||
protected override (string, string) String1 => ("Removes", "from");
|
protected override (string, string) String1 => ("Removes", "from");
|
||||||
protected override string String2 => "unset";
|
protected override string String2 => "unset";
|
||||||
public RoleDel(ModCommands module, JObject config) : base(module, config) { }
|
|
||||||
protected override async Task ContinueInvoke(SocketGuildUser target, SocketRole role) => await target.RemoveRoleAsync(role);
|
protected override async Task ContinueInvoke(SocketGuildUser target, SocketRole role) => await target.RemoveRoleAsync(role);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,7 +44,7 @@ abstract class RoleManipulation : CommandConfig {
|
||||||
|
|
||||||
public override async Task Invoke(SocketGuild g, SocketMessage msg) {
|
public override async Task Invoke(SocketGuild g, SocketMessage msg) {
|
||||||
// TODO reason in further parameters?
|
// TODO reason in further parameters?
|
||||||
var line = msg.Content.Split(new char[] { ' ' }, 3, StringSplitOptions.RemoveEmptyEntries);
|
var line = SplitToParams(msg, 3);
|
||||||
string targetstr;
|
string targetstr;
|
||||||
if (line.Length < 2) {
|
if (line.Length < 2) {
|
||||||
await SendUsageMessageAsync(msg.Channel, null);
|
await SendUsageMessageAsync(msg.Channel, null);
|
||||||
|
|
|
@ -13,7 +13,7 @@ class Say : CommandConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task Invoke(SocketGuild g, SocketMessage msg) {
|
public override async Task Invoke(SocketGuild g, SocketMessage msg) {
|
||||||
var line = msg.Content.Split(new char[] { ' ' }, 3, StringSplitOptions.RemoveEmptyEntries);
|
var line = SplitToParams(msg, 3);
|
||||||
if (line.Length <= 1) {
|
if (line.Length <= 1) {
|
||||||
await SendUsageMessageAsync(msg.Channel, ":x: You must specify a channel.");
|
await SendUsageMessageAsync(msg.Channel, ":x: You must specify a channel.");
|
||||||
return;
|
return;
|
||||||
|
@ -23,7 +23,7 @@ class Say : CommandConfig {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var getCh = Utilities.ChannelMention.Match(line[1]);
|
var getCh = Utilities.ChannelMentionRegex().Match(line[1]);
|
||||||
if (!getCh.Success) {
|
if (!getCh.Success) {
|
||||||
await SendUsageMessageAsync(msg.Channel, ":x: Unable to find given channel.");
|
await SendUsageMessageAsync(msg.Channel, ":x: Unable to find given channel.");
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -20,7 +20,7 @@ class ShowModLogs : CommandConfig {
|
||||||
|
|
||||||
// Usage: (command) (query) [page]
|
// Usage: (command) (query) [page]
|
||||||
public override async Task Invoke(SocketGuild g, SocketMessage msg) {
|
public override async Task Invoke(SocketGuild g, SocketMessage msg) {
|
||||||
var line = msg.Content.Split(new char[] { ' ' }, 3, StringSplitOptions.RemoveEmptyEntries);
|
var line = SplitToParams(msg, 3);
|
||||||
if (line.Length < 2) {
|
if (line.Length < 2) {
|
||||||
await SendUsageMessageAsync(msg.Channel, null);
|
await SendUsageMessageAsync(msg.Channel, null);
|
||||||
return;
|
return;
|
||||||
|
@ -47,13 +47,12 @@ class ShowModLogs : CommandConfig {
|
||||||
.Where(l => l.GuildId == query.GuildId && l.UserId == query.UserId)
|
.Where(l => l.GuildId == query.GuildId && l.UserId == query.UserId)
|
||||||
.Count();
|
.Count();
|
||||||
totalPages = (int)Math.Ceiling((double)totalEntries / LogEntriesPerMessage);
|
totalPages = (int)Math.Ceiling((double)totalEntries / LogEntriesPerMessage);
|
||||||
results = db.ModLogs
|
results = [.. db.ModLogs
|
||||||
.Where(l => l.GuildId == query.GuildId && l.UserId == query.UserId)
|
.Where(l => l.GuildId == query.GuildId && l.UserId == query.UserId)
|
||||||
.OrderByDescending(l => l.LogId)
|
.OrderByDescending(l => l.LogId)
|
||||||
.Skip((pagenum - 1) * LogEntriesPerMessage)
|
.Skip((pagenum - 1) * LogEntriesPerMessage)
|
||||||
.Take(LogEntriesPerMessage)
|
.Take(LogEntriesPerMessage)
|
||||||
.AsNoTracking()
|
.AsNoTracking()];
|
||||||
.ToList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var resultList = new EmbedBuilder() {
|
var resultList = new EmbedBuilder() {
|
||||||
|
|
|
@ -26,7 +26,7 @@ class Timeout : CommandConfig {
|
||||||
|
|
||||||
// Usage: (command) (user) (duration) (reason)
|
// Usage: (command) (user) (duration) (reason)
|
||||||
public override async Task Invoke(SocketGuild g, SocketMessage msg) {
|
public override async Task Invoke(SocketGuild g, SocketMessage msg) {
|
||||||
var line = msg.Content.Split(new char[] { ' ' }, 4, StringSplitOptions.RemoveEmptyEntries);
|
var line = SplitToParams(msg, 4);
|
||||||
string targetstr;
|
string targetstr;
|
||||||
string? reason;
|
string? reason;
|
||||||
if (line.Length < 3) {
|
if (line.Length < 3) {
|
||||||
|
|
|
@ -16,7 +16,7 @@ class Unban : CommandConfig {
|
||||||
|
|
||||||
// Usage: (command) (user query)
|
// Usage: (command) (user query)
|
||||||
public override async Task Invoke(SocketGuild g, SocketMessage msg) {
|
public override async Task Invoke(SocketGuild g, SocketMessage msg) {
|
||||||
var line = msg.Content.Split(new char[] { ' ' }, 3, StringSplitOptions.RemoveEmptyEntries);
|
var line = SplitToParams(msg, 3);
|
||||||
string targetstr;
|
string targetstr;
|
||||||
if (line.Length < 2) {
|
if (line.Length < 2) {
|
||||||
await SendUsageMessageAsync(msg.Channel, null);
|
await SendUsageMessageAsync(msg.Channel, null);
|
||||||
|
|
|
@ -16,7 +16,7 @@ class Untimeout : CommandConfig {
|
||||||
|
|
||||||
// Usage: (command) (user query)
|
// Usage: (command) (user query)
|
||||||
public override async Task Invoke(SocketGuild g, SocketMessage msg) {
|
public override async Task Invoke(SocketGuild g, SocketMessage msg) {
|
||||||
var line = msg.Content.Split(new char[] { ' ' }, 3, StringSplitOptions.RemoveEmptyEntries);
|
var line = SplitToParams(msg, 3);
|
||||||
string targetstr;
|
string targetstr;
|
||||||
if (line.Length < 2) {
|
if (line.Length < 2) {
|
||||||
await SendUsageMessageAsync(msg.Channel, null);
|
await SendUsageMessageAsync(msg.Channel, null);
|
||||||
|
|
|
@ -17,9 +17,9 @@ class ModuleConfig {
|
||||||
Label = def[nameof(Label)]?.Value<string>()
|
Label = def[nameof(Label)]?.Value<string>()
|
||||||
?? throw new ModuleLoadException($"'{nameof(Label)}' was not defined in a command definition.");
|
?? throw new ModuleLoadException($"'{nameof(Label)}' was not defined in a command definition.");
|
||||||
var cmd = CreateCommandInstance(instance, def);
|
var cmd = CreateCommandInstance(instance, def);
|
||||||
if (commands.ContainsKey(cmd.Command)) {
|
if (commands.TryGetValue(cmd.Command, out CommandConfig? existing)) {
|
||||||
throw new ModuleLoadException(
|
throw new ModuleLoadException(
|
||||||
$"{Label}: The command name '{cmd.Command}' is already in use by '{commands[cmd.Command].Label}'.");
|
$"{Label}: The command name '{cmd.Command}' is already in use by '{existing.Label}'.");
|
||||||
}
|
}
|
||||||
commands.Add(cmd.Command, cmd);
|
commands.Add(cmd.Command, cmd);
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,5 @@ class ModuleConfig {
|
||||||
} catch (FormatException) {
|
} catch (FormatException) {
|
||||||
throw new ModuleLoadException("Name specified in configuration is not a role.");
|
throw new ModuleLoadException("Name specified in configuration is not a role.");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,7 +33,7 @@ class ResponseExecutor {
|
||||||
_user = (SocketGuildUser)msg.Author;
|
_user = (SocketGuildUser)msg.Author;
|
||||||
_guild = _user.Guild;
|
_guild = _user.Guild;
|
||||||
|
|
||||||
_reports = new();
|
_reports = [];
|
||||||
Log = logger;
|
Log = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,9 +23,7 @@ class ModuleConfig {
|
||||||
} catch (FormatException) {
|
} catch (FormatException) {
|
||||||
throw new ModuleLoadException($"'{item.Name}' is not specified as a role.");
|
throw new ModuleLoadException($"'{item.Name}' is not specified as a role.");
|
||||||
}
|
}
|
||||||
var role = name.FindRoleIn(g);
|
var role = name.FindRoleIn(g) ?? throw new ModuleLoadException($"Unable to find role '{name}'.");
|
||||||
if (role == null) throw new ModuleLoadException($"Unable to find role '{name}'.");
|
|
||||||
|
|
||||||
var channels = Utilities.LoadStringOrStringArray(item.Value);
|
var channels = Utilities.LoadStringOrStringArray(item.Value);
|
||||||
if (channels.Count == 0) throw new ModuleLoadException($"One or more channels must be defined under '{name}'.");
|
if (channels.Count == 0) throw new ModuleLoadException($"One or more channels must be defined under '{name}'.");
|
||||||
foreach (var id in channels) {
|
foreach (var id in channels) {
|
||||||
|
|
|
@ -1,29 +1,31 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>Exe</OutputType>
|
<Version>3.2.1</Version>
|
||||||
<TargetFramework>net6.0</TargetFramework>
|
|
||||||
<Authors>NoiTheCat</Authors>
|
<Authors>NoiTheCat</Authors>
|
||||||
<Description>Advanced and flexible Discord moderation bot.</Description>
|
<Description>Advanced and flexible Discord moderation bot.</Description>
|
||||||
<Version>3.2.0</Version>
|
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<GenerateDocumentationFile>True</GenerateDocumentationFile>
|
<GenerateDocumentationFile>True</GenerateDocumentationFile>
|
||||||
|
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="CommandLineParser" Version="2.9.1" />
|
<PackageReference Include="CommandLineParser" Version="2.9.1" />
|
||||||
<PackageReference Include="Discord.Net" Version="3.12.0" />
|
<PackageReference Include="Discord.Net" Version="3.15.0" />
|
||||||
<PackageReference Include="EFCore.NamingConventions" Version="6.0.0" />
|
<PackageReference Include="EFCore.NamingConventions" Version="8.0.3" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.7" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.6" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.7">
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.6">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.7" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.6" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||||
<PackageReference Include="Npgsql" Version="6.0.7" />
|
<PackageReference Include="Npgsql" Version="8.0.3" />
|
||||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="6.0.7" />
|
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.4" />
|
||||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL.Design" Version="1.1.0" />
|
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL.Design" Version="1.1.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
|
@ -10,24 +10,20 @@ namespace RegexBot;
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// Implementing classes should not rely on local variables to store runtime or state data for guilds.
|
/// Implementing classes should not rely on local variables to store runtime or state data for guilds.
|
||||||
/// Instead, use <see cref="CreateGuildStateAsync"/> and <see cref="GetGuildState"/>.
|
/// Instead, use <see cref="CreateGuildStateAsync"/> and <see cref="GetGuildState"/>.
|
||||||
|
/// <br/><br/>
|
||||||
|
/// Additionally, do not assume that <see cref="DiscordClient"/> is available during the constructor.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public abstract class RegexbotModule {
|
public abstract class RegexbotModule(RegexbotClient bot) {
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Retrieves the bot instance.
|
/// Retrieves the bot instance.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public RegexbotClient Bot { get; }
|
public RegexbotClient Bot { get; } = bot;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Retrieves the Discord client instance.
|
/// Retrieves the Discord client instance.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public DiscordSocketClient DiscordClient { get => Bot.DiscordClient; }
|
public DiscordSocketClient DiscordClient { get => Bot.DiscordClient; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Called when a module is being loaded.
|
|
||||||
/// At this point, all bot services are available, but Discord is not. Do not use <see cref="DiscordClient"/>.
|
|
||||||
/// </summary>
|
|
||||||
public RegexbotModule(RegexbotClient bot) => Bot = bot;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the name of this module.
|
/// Gets the name of this module.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -6,10 +6,9 @@ namespace RegexBot.Services.CommonFunctions {
|
||||||
/// functions may help enforce a sense of consistency across modules when performing common actions, and may
|
/// functions may help enforce a sense of consistency across modules when performing common actions, and may
|
||||||
/// inform services which provide any additional features the ability to respond to those actions ahead of time.
|
/// inform services which provide any additional features the ability to respond to those actions ahead of time.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal partial class CommonFunctionsService : Service {
|
internal partial class CommonFunctionsService(RegexbotClient bot) : Service(bot) {
|
||||||
// Note: Several classes within this service created by its hooks are meant to be sent to modules,
|
// Note: Several classes within this service created by its hooks are meant to be sent to modules,
|
||||||
// therefore those public classes are placed into the root RegexBot namespace for the developer's convenience.
|
// therefore those public classes are placed into the root RegexBot namespace for the developer's convenience.
|
||||||
public CommonFunctionsService(RegexbotClient bot) : base(bot) { }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -86,7 +86,7 @@ class UserCachingSubservice {
|
||||||
if (sID.HasValue)
|
if (sID.HasValue)
|
||||||
query = query.Where(c => c.UserId == (long)sID.Value);
|
query = query.Where(c => c.UserId == (long)sID.Value);
|
||||||
if (nameSearch != null) {
|
if (nameSearch != null) {
|
||||||
query = query.Where(c => c.Username.ToLower() == nameSearch.Value.name.ToLower());
|
query = query.Where(c => c.Username.Equals(nameSearch.Value.name, StringComparison.OrdinalIgnoreCase));
|
||||||
if (nameSearch.Value.disc != null) query = query.Where(c => c.Discriminator == nameSearch.Value.disc);
|
if (nameSearch.Value.disc != null) query = query.Where(c => c.Discriminator == nameSearch.Value.disc);
|
||||||
}
|
}
|
||||||
query = query.OrderByDescending(e => e.ULastUpdateTime);
|
query = query.OrderByDescending(e => e.ULastUpdateTime);
|
||||||
|
@ -95,7 +95,7 @@ class UserCachingSubservice {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Is search actually a ping? Extract ID.
|
// Is search actually a ping? Extract ID.
|
||||||
var m = Utilities.UserMention.Match(search);
|
var m = Utilities.UserMentionRegex().Match(search);
|
||||||
if (m.Success) search = m.Groups["snowflake"].Value;
|
if (m.Success) search = m.Groups["snowflake"].Value;
|
||||||
|
|
||||||
// Is search a number? Assume ID, proceed to query.
|
// Is search a number? Assume ID, proceed to query.
|
||||||
|
@ -117,8 +117,9 @@ class UserCachingSubservice {
|
||||||
if (sID.HasValue)
|
if (sID.HasValue)
|
||||||
query = query.Where(c => c.UserId == (long)sID.Value);
|
query = query.Where(c => c.UserId == (long)sID.Value);
|
||||||
if (nameSearch != null) {
|
if (nameSearch != null) {
|
||||||
query = query.Where(c => (c.Nickname != null && c.Nickname.ToLower() == nameSearch.Value.name.ToLower()) ||
|
query = query.Where(c => (c.Nickname != null
|
||||||
c.User.Username.ToLower() == nameSearch.Value.name.ToLower());
|
&& c.Nickname.Equals(nameSearch.Value.name, StringComparison.OrdinalIgnoreCase))
|
||||||
|
|| c.User.Username.Equals(nameSearch.Value.name, StringComparison.OrdinalIgnoreCase));
|
||||||
if (nameSearch.Value.disc != null) query = query.Where(c => c.User.Discriminator == nameSearch.Value.disc);
|
if (nameSearch.Value.disc != null) query = query.Where(c => c.User.Discriminator == nameSearch.Value.disc);
|
||||||
}
|
}
|
||||||
query = query.OrderByDescending(e => e.GULastUpdateTime);
|
query = query.OrderByDescending(e => e.GULastUpdateTime);
|
||||||
|
@ -127,7 +128,7 @@ class UserCachingSubservice {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Is search actually a ping? Extract ID.
|
// Is search actually a ping? Extract ID.
|
||||||
var m = Utilities.UserMention.Match(search);
|
var m = Utilities.UserMentionRegex().Match(search);
|
||||||
if (m.Success) search = m.Groups["snowflake"].Value;
|
if (m.Success) search = m.Groups["snowflake"].Value;
|
||||||
|
|
||||||
// Is search a number? Assume ID, proceed to query.
|
// Is search a number? Assume ID, proceed to query.
|
||||||
|
@ -144,7 +145,7 @@ class UserCachingSubservice {
|
||||||
private static (string, string?) SplitNameAndDiscriminator(string input) {
|
private static (string, string?) SplitNameAndDiscriminator(string input) {
|
||||||
string name;
|
string name;
|
||||||
string? disc = null;
|
string? disc = null;
|
||||||
var split = Utilities.DiscriminatorSearch.Match(input);
|
var split = Utilities.DiscriminatorSearchRegex().Match(input);
|
||||||
if (split.Success) {
|
if (split.Success) {
|
||||||
name = split.Groups[1].Value;
|
name = split.Groups[1].Value;
|
||||||
disc = split.Groups[2].Value;
|
disc = split.Groups[2].Value;
|
||||||
|
|
|
@ -52,7 +52,7 @@ class LoggingService : Service {
|
||||||
var now = DateTimeOffset.Now;
|
var now = DateTimeOffset.Now;
|
||||||
var output = new StringBuilder();
|
var output = new StringBuilder();
|
||||||
var prefix = $"[{now:s}] [{source}] ";
|
var prefix = $"[{now:s}] [{source}] ";
|
||||||
foreach (var line in message.Split(new string[] { "\r\n", "\n" }, StringSplitOptions.None)) {
|
foreach (var line in message.Split(["\r\n", "\n"], StringSplitOptions.None)) {
|
||||||
output.Append(prefix).AppendLine(line);
|
output.Append(prefix).AppendLine(line);
|
||||||
}
|
}
|
||||||
var outstr = output.ToString();
|
var outstr = output.ToString();
|
||||||
|
|
|
@ -6,12 +6,12 @@ namespace RegexBot.Services.ModuleState;
|
||||||
/// </summary>
|
/// </summary>
|
||||||
class ModuleStateService : Service {
|
class ModuleStateService : Service {
|
||||||
private readonly Dictionary<ulong, EntityList> _moderators;
|
private readonly Dictionary<ulong, EntityList> _moderators;
|
||||||
private readonly Dictionary<ulong, Dictionary<Type, object?>> _stateData;
|
private readonly Dictionary<ulong, Dictionary<Type, object?>> _guildStates;
|
||||||
private readonly JObject _serverConfs;
|
private readonly JObject _serverConfs;
|
||||||
|
|
||||||
public ModuleStateService(RegexbotClient bot, JObject servers) : base(bot) {
|
public ModuleStateService(RegexbotClient bot, JObject servers) : base(bot) {
|
||||||
_moderators = new();
|
_moderators = [];
|
||||||
_stateData = new();
|
_guildStates = [];
|
||||||
_serverConfs = servers;
|
_serverConfs = servers;
|
||||||
|
|
||||||
bot.DiscordClient.GuildAvailable += RefreshGuildState;
|
bot.DiscordClient.GuildAvailable += RefreshGuildState;
|
||||||
|
@ -25,17 +25,20 @@ class ModuleStateService : Service {
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task RemoveGuildData(SocketGuild arg) {
|
private Task RemoveGuildData(SocketGuild arg) {
|
||||||
_stateData.Remove(arg.Id);
|
_guildStates.Remove(arg.Id);
|
||||||
_moderators.Remove(arg.Id);
|
_moderators.Remove(arg.Id);
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hooked
|
// Hooked
|
||||||
public T? DoGetStateObj<T>(ulong guildId, Type t) {
|
public T? DoGetStateObj<T>(ulong guildId, Type t) {
|
||||||
if (_stateData.ContainsKey(guildId) && _stateData[guildId].ContainsKey(t)) {
|
if (_guildStates.TryGetValue(guildId, out var guildConfs)) {
|
||||||
// Leave handling of potential InvalidCastException to caller.
|
if (guildConfs.TryGetValue(t, out var moduleConf)) {
|
||||||
return (T?)_stateData[guildId][t];
|
// Leave handling of potential InvalidCastException to caller.
|
||||||
|
return (T?)moduleConf;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return default;
|
return default;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,7 +74,7 @@ class ModuleStateService : Service {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_moderators[guild.Id] = mods;
|
_moderators[guild.Id] = mods;
|
||||||
_stateData[guild.Id] = newStates;
|
_guildStates[guild.Id] = newStates;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,13 +6,11 @@
|
||||||
/// Services provide core and shared functionality for this program. Modules are expected to call into services
|
/// Services provide core and shared functionality for this program. Modules are expected to call into services
|
||||||
/// directly or indirectly in order to access bot features.
|
/// directly or indirectly in order to access bot features.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
internal abstract class Service {
|
internal abstract class Service(RegexbotClient bot) {
|
||||||
public RegexbotClient BotClient { get; }
|
public RegexbotClient BotClient { get; } = bot;
|
||||||
|
|
||||||
public string Name => GetType().Name;
|
public string Name => GetType().Name;
|
||||||
|
|
||||||
public Service(RegexbotClient bot) => BotClient = bot;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Emits a log message.
|
/// Emits a log message.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
Loading…
Reference in a new issue