@@ -0,0 +1,242 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
namespace Discord; | |||
/// <summary> | |||
/// A builder used to create a <see cref="IAutoModRule"/>. | |||
/// </summary> | |||
public class AutoModRuleBuilder | |||
{ | |||
private const int MaxKeywordCount = 1000; | |||
private const int MaxKeywordLength = 30; | |||
private const int MaxRegexPatternCount = 10; | |||
private const int MaxRegexPatternLength = 260; | |||
private const int MaxAllowListCountKeyword = 100; | |||
private const int MaxAllowListCountKeywordPreset = 1000; | |||
private const int MaxAllowListEntryLength = 30; | |||
private const int MaxMentionLimit = 50; | |||
private const int MaxExemptRoles = 20; | |||
private const int MaxExemptChannels = 50; | |||
private List<string> _keywordFilter = new (); | |||
private List<string> _regexPatterns = new (); | |||
private List<string> _allowList = new (); | |||
private List<AutoModRuleActionBuilder> _actions = new (); | |||
private List<ulong> _exemptRoles = new(); | |||
private List<ulong> _exemptChannels = new(); | |||
private int? _mentionLimit; | |||
/// <summary> | |||
/// | |||
/// </summary> | |||
public List<string> KeywordFilter | |||
{ | |||
get { return _keywordFilter; } | |||
set | |||
{ | |||
if (TriggerType != AutoModTriggerType.Keyword) | |||
throw new ArgumentException(message: $"Keyword filter can only be used with 'Keyword' trigger type.", paramName: nameof(KeywordFilter)); | |||
if (value.Count > MaxKeywordCount) | |||
throw new ArgumentException(message: $"Keyword count must be less than or equal to {MaxKeywordCount}.", paramName: nameof(KeywordFilter)); | |||
if (value.Any(x => x.Length > MaxKeywordLength)) | |||
throw new ArgumentException(message: $"Keyword length must be less than or equal to {MaxKeywordLength}.", paramName: nameof(KeywordFilter)); | |||
_keywordFilter = value; | |||
} | |||
} | |||
/// <summary> | |||
/// | |||
/// </summary> | |||
public List<string> RegexPatterns | |||
{ | |||
get { return _regexPatterns; } | |||
set | |||
{ | |||
if (TriggerType != AutoModTriggerType.Keyword) | |||
throw new ArgumentException(message: $"Regex patterns can only be used with 'Keyword' trigger type.", paramName: nameof(RegexPatterns)); | |||
if (value.Count > MaxRegexPatternCount) | |||
throw new ArgumentException(message: $"Regex pattern count must be less than or equal to {MaxRegexPatternCount}.", paramName: nameof(RegexPatterns)); | |||
if (value.Any(x => x.Length > MaxRegexPatternLength)) | |||
throw new ArgumentException(message: $"Regex pattern must be less than or equal to {MaxRegexPatternLength}.", paramName: nameof(RegexPatterns)); | |||
_regexPatterns = value; | |||
} | |||
} | |||
/// <summary> | |||
/// | |||
/// </summary> | |||
public List<string> AllowList | |||
{ | |||
get { return _allowList; } | |||
set | |||
{ | |||
if (TriggerType is not AutoModTriggerType.Keyword or AutoModTriggerType.KeywordPreset) | |||
throw new ArgumentException(message: $"Allow list can only be used with 'Keyword' or 'KeywordPreset' trigger type.", paramName: nameof(AllowList)); | |||
if (TriggerType == AutoModTriggerType.Keyword && value.Count > MaxAllowListCountKeyword) | |||
throw new ArgumentException(message: $"Allow list entry count must be less than or equal to {MaxAllowListCountKeyword}.", paramName: nameof(AllowList)); | |||
if (TriggerType == AutoModTriggerType.KeywordPreset && value.Count > MaxAllowListCountKeywordPreset) | |||
throw new ArgumentException(message: $"Allow list entry count must be less than or equal to {MaxAllowListCountKeywordPreset}.", paramName: nameof(AllowList)); | |||
if (value.Any(x => x.Length > MaxAllowListEntryLength)) | |||
throw new ArgumentException(message: $"Allow list entry length must be less than or equal to {MaxAllowListEntryLength}.", paramName: nameof(AllowList)); | |||
_allowList = value; | |||
} | |||
} | |||
/// <summary> | |||
/// | |||
/// </summary> | |||
public List<ulong> ExemptRoles | |||
{ | |||
get { return _exemptRoles; } | |||
set | |||
{ | |||
if (value.Count > MaxExemptRoles) | |||
throw new ArgumentException(message: $"Exempt roles count must be less than or equal to {MaxExemptRoles}.", paramName: nameof(RegexPatterns)); | |||
_exemptRoles = value; | |||
} | |||
} | |||
/// <summary> | |||
/// | |||
/// </summary> | |||
public List<ulong> ExemptChannels | |||
{ | |||
get { return _exemptChannels; } | |||
set | |||
{ | |||
if (value.Count > MaxExemptRoles) | |||
throw new ArgumentException(message: $"Exempt channels count must be less than or equal to {MaxExemptRoles}.", paramName: nameof(RegexPatterns)); | |||
_exemptChannels = value; | |||
} | |||
} | |||
/// <summary> | |||
/// | |||
/// </summary> | |||
public HashSet<KeywordPresetTypes> Presets = new (); | |||
/// <summary> | |||
/// | |||
/// </summary> | |||
public string Name { get; set; } | |||
/// <summary> | |||
/// | |||
/// </summary> | |||
public AutoModEventType EventType { get; set; } | |||
/// <summary> | |||
/// | |||
/// </summary> | |||
public AutoModTriggerType TriggerType { get; } | |||
/// <summary> | |||
/// | |||
/// </summary> | |||
public int? MentionLimit | |||
{ | |||
get => _mentionLimit; | |||
set | |||
{ | |||
if (TriggerType != AutoModTriggerType.Keyword) | |||
throw new ArgumentException(message: $"MentionLimit can only be used with 'MentionSpam' trigger type.", paramName: nameof(MentionLimit)); | |||
if (value is not null && value > MaxMentionLimit) | |||
throw new ArgumentException(message: $"Mention limit must be less than or equal to {MaxMentionLimit}.", paramName: nameof(MentionLimit)); | |||
_mentionLimit = value; | |||
} | |||
} | |||
/// <summary> | |||
/// | |||
/// </summary> | |||
public List<AutoModRuleActionBuilder> Actions; | |||
/// <summary> | |||
/// | |||
/// </summary> | |||
public bool Enabled { get; set; } = false; | |||
/// <summary> | |||
/// | |||
/// </summary> | |||
/// <param name="type"></param> | |||
public AutoModRuleBuilder(AutoModTriggerType type) | |||
{ | |||
TriggerType = type; | |||
} | |||
} | |||
/// <summary> | |||
/// Represents an action that will be preformed if a user breaks an <see cref="IAutoModRule"/>. | |||
/// </summary> | |||
public class AutoModRuleActionBuilder | |||
{ | |||
private const int MaxTimeoutSeconds = 2419200; | |||
private TimeSpan? _timeoutDuration; | |||
/// <summary> | |||
/// Gets or sets the type for this action. | |||
/// </summary> | |||
public AutoModActionType Type { get; } | |||
/// <summary> | |||
/// Get or sets the channel id on which to post alerts. | |||
/// </summary> | |||
public ulong? ChannelId { get; set; } | |||
/// <summary> | |||
/// Gets or sets the duration of which a user will be timed out for breaking this rule. | |||
/// </summary> | |||
public TimeSpan? TimeoutDuration | |||
{ | |||
get => _timeoutDuration; | |||
set | |||
{ | |||
if(value is { TotalSeconds: > MaxTimeoutSeconds }) | |||
throw new ArgumentException(message: $"Field count must be less than or equal to {MaxTimeoutSeconds}.", paramName: nameof(TimeoutDuration)); | |||
_timeoutDuration = value; | |||
} | |||
} | |||
/// <summary> | |||
/// | |||
/// </summary> | |||
public AutoModRuleActionBuilder() | |||
{ | |||
} | |||
/// <summary> | |||
/// | |||
/// </summary> | |||
public AutoModRuleActionBuilder(AutoModActionType type, ulong? channelId, TimeSpan? duration) | |||
{ | |||
Type = type; | |||
ChannelId = channelId; | |||
TimeoutDuration = duration; | |||
} | |||
} |
@@ -1290,6 +1290,6 @@ namespace Discord | |||
/// <returns> | |||
/// A task that represents the asynchronous creation operation. The task result contains the created <see cref="WelcomeScreen"/>. | |||
/// </returns> | |||
Task<IAutoModRule> CreateAutoModRuleAsync(RequestOptions options = null); | |||
Task<IAutoModRule> CreateAutoModRuleAsync(AutoModRuleBuilder builder, RequestOptions options = null); | |||
} | |||
} |
@@ -1,3 +1,4 @@ | |||
using Newtonsoft.Json; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
@@ -8,13 +9,28 @@ namespace Discord.API.Rest | |||
{ | |||
internal class CreateAutoModRuleParams | |||
{ | |||
[JsonProperty("name")] | |||
public string Name { get; set; } | |||
[JsonProperty("event_type")] | |||
public AutoModEventType EventType { get; set; } | |||
[JsonProperty("trigger_type")] | |||
public AutoModTriggerType TriggerType { get; set; } | |||
[JsonProperty("trigger_metadata")] | |||
public Optional<TriggerMetadata> TriggerMetadata { get; set; } | |||
[JsonProperty("actions")] | |||
public AutoModAction[] Actions { get; set; } | |||
[JsonProperty("enabled")] | |||
public Optional<bool> Enabled { get; set; } | |||
[JsonProperty("exempt_roles")] | |||
public Optional<ulong[]> ExemptRoles { get; set; } | |||
[JsonProperty("exempt_channels")] | |||
public Optional<ulong[]> ExemptChannels { get; set; } | |||
} | |||
} |
@@ -1064,10 +1064,8 @@ namespace Discord.Rest | |||
#region Auto Mod | |||
public static Task<AutoModerationRule> CreateAutoModRuleAsync(IGuild guild, BaseDiscordClient client, RequestOptions options) | |||
{ | |||
throw new NotImplementedException(); | |||
} | |||
public static async Task<AutoModerationRule> CreateAutoModRuleAsync(IGuild guild, CreateAutoModRuleParams args, BaseDiscordClient client, RequestOptions options) | |||
=> await client.ApiClient.CreateGuildAutoModRuleAsync(guild.Id, args, options); | |||
public static async Task<AutoModerationRule> GetAutoModRuleAsync(ulong ruleId, IGuild guild, BaseDiscordClient client, RequestOptions options) | |||
=> await client.ApiClient.GetGuildAutoModRuleAsync(guild.Id, ruleId, options); | |||
@@ -89,8 +89,13 @@ public class RestAutoModRule : RestEntity<ulong>, IAutoModRule | |||
} | |||
/// <inheritdoc /> | |||
public Task ModifyAsync(Action<AutoModRuleProperties> func, RequestOptions options = null) => throw new NotImplementedException(); | |||
public async Task ModifyAsync(Action<AutoModRuleProperties> func, RequestOptions options = null) | |||
{ | |||
var model = await GuildHelper.ModifyRuleAsync(Discord, this, func, options); | |||
Update(model); | |||
} | |||
/// <inheritdoc /> | |||
public Task DeleteAsync(RequestOptions options = null) => throw new NotImplementedException(); | |||
public Task DeleteAsync(RequestOptions options = null) | |||
=> GuildHelper.DeleteRuleAsync(Discord, this, options); | |||
} |
@@ -1217,10 +1217,11 @@ namespace Discord.Rest | |||
} | |||
/// <inheritdoc cref="IGuild.CreateAutoModRuleAsync"/> | |||
public async Task<RestAutoModRule> CreateAutoModRuleAsync(RequestOptions options = null) | |||
public async Task<RestAutoModRule> CreateAutoModRuleAsync(AutoModRuleBuilder builder, RequestOptions options = null) | |||
{ | |||
var rule = await GuildHelper.CreateAutoModRuleAsync(this, Discord, options); | |||
throw new NotImplementedException(); | |||
var rule = await GuildHelper.CreateAutoModRuleAsync(this, builder.ToProperties(), Discord, options); | |||
return RestAutoModRule.Create(Discord, rule); | |||
} | |||
@@ -1580,8 +1581,8 @@ namespace Discord.Rest | |||
=> await GetAutoModRulesAsync(options).ConfigureAwait(false); | |||
/// <inheritdoc/> | |||
async Task<IAutoModRule> IGuild.CreateAutoModRuleAsync(RequestOptions options) | |||
=> await CreateAutoModRuleAsync(options).ConfigureAwait(false); | |||
async Task<IAutoModRule> IGuild.CreateAutoModRuleAsync(AutoModRuleBuilder builder, RequestOptions options) | |||
=> await CreateAutoModRuleAsync(builder, options).ConfigureAwait(false); | |||
#endregion | |||
} | |||
@@ -214,5 +214,34 @@ namespace Discord.Rest | |||
Id = interaction.Id, | |||
}; | |||
} | |||
public static API.Rest.CreateAutoModRuleParams ToProperties(this AutoModRuleBuilder builder) | |||
{ | |||
return new API.Rest.CreateAutoModRuleParams | |||
{ | |||
TriggerMetadata = new API.TriggerMetadata | |||
{ | |||
KeywordFilter = builder.KeywordFilter?.ToArray(), | |||
AllowList = builder.AllowList?.ToArray(), | |||
MentionLimit = builder.MentionLimit ?? Optional<int>.Unspecified, | |||
Presets = builder.Presets?.ToArray(), | |||
RegexPatterns = builder.RegexPatterns?.ToArray(), | |||
}, | |||
TriggerType = builder.TriggerType, | |||
Actions = builder.Actions?.Select(x => new API.AutoModAction | |||
{ | |||
Metadata = new API.ActionMetadata | |||
{ | |||
ChannelId = x.ChannelId ?? Optional<ulong>.Unspecified, | |||
DurationSeconds = (int?)x.TimeoutDuration?.TotalSeconds ?? Optional<int>.Unspecified | |||
}, | |||
Type = x.Type, | |||
}).ToArray(), | |||
Enabled = builder.Enabled, | |||
EventType = builder.EventType, | |||
ExemptChannels = builder.ExemptChannels?.ToArray() ?? Optional<ulong[]>.Unspecified, | |||
ExemptRoles = builder.ExemptRoles?.ToArray() ?? Optional <ulong[]>.Unspecified, | |||
}; | |||
} | |||
} | |||
} |
@@ -3,7 +3,6 @@ using System; | |||
using System.Collections.Generic; | |||
using System.Collections.Immutable; | |||
using System.Linq; | |||
using System.Text; | |||
using System.Threading.Tasks; | |||
using Model = Discord.API.AutoModerationRule; | |||
@@ -101,8 +100,11 @@ namespace Discord.WebSocket | |||
} | |||
/// <inheritdoc/> | |||
public Task ModifyAsync(Action<AutoModRuleProperties> func, RequestOptions options = null) | |||
=> GuildHelper.ModifyRuleAsync(Discord, this, func, options); | |||
public async Task ModifyAsync(Action<AutoModRuleProperties> func, RequestOptions options = null) | |||
{ | |||
var model = await GuildHelper.ModifyRuleAsync(Discord, this, func, options); | |||
Guild.AddOrUpdateAutoModRule(model); | |||
} | |||
/// <inheritdoc/> | |||
public Task DeleteAsync(RequestOptions options = null) | |||
@@ -1827,7 +1827,10 @@ namespace Discord.WebSocket | |||
return socketRule; | |||
} | |||
internal SocketAutoModRule GetAutoModRule(ulong id) | |||
/// <summary> | |||
/// Gets a single rule configured in a guild from cache. Returns <see langword="null"/> if the rule was not found. | |||
/// </summary> | |||
public SocketAutoModRule GetAutoModRule(ulong id) | |||
{ | |||
return _automodRules.TryGetValue(id, out var rule) ? rule : null; | |||
} | |||
@@ -1864,10 +1867,11 @@ namespace Discord.WebSocket | |||
} | |||
/// <inheritdoc cref="IGuild.CreateAutoModRuleAsync"/> | |||
public async Task<SocketAutoModRule> CreateAutoModRuleAsync(RequestOptions options = null) | |||
public async Task<SocketAutoModRule> CreateAutoModRuleAsync(AutoModRuleBuilder builder, RequestOptions options = null) | |||
{ | |||
var rule = await GuildHelper.CreateAutoModRuleAsync(this, Discord, options); | |||
throw new NotImplementedException(); | |||
var rule = await GuildHelper.CreateAutoModRuleAsync(this, builder.ToProperties(), Discord, options); | |||
return AddOrUpdateAutoModRule(rule); | |||
} | |||
#endregion | |||
@@ -2126,8 +2130,8 @@ namespace Discord.WebSocket | |||
=> await GetAutoModRulesAsync(options).ConfigureAwait(false); | |||
/// <inheritdoc/> | |||
async Task<IAutoModRule> IGuild.CreateAutoModRuleAsync(RequestOptions options) | |||
=> await CreateAutoModRuleAsync(options).ConfigureAwait(false); | |||
async Task<IAutoModRule> IGuild.CreateAutoModRuleAsync(AutoModRuleBuilder builder, RequestOptions options) | |||
=> await CreateAutoModRuleAsync(builder, options).ConfigureAwait(false); | |||
#endregion | |||
} | |||