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