diff --git a/src/Discord.Net.Core/GatewayIntents.cs b/src/Discord.Net.Core/GatewayIntents.cs
index e9dd8f814..b9d715659 100644
--- a/src/Discord.Net.Core/GatewayIntents.cs
+++ b/src/Discord.Net.Core/GatewayIntents.cs
@@ -48,13 +48,25 @@ namespace Discord
/// This intent includes GUILD_SCHEDULED_EVENT_CREATE, GUILD_SCHEDULED_EVENT_UPDATE, GUILD_SCHEDULED_EVENT_DELETE, GUILD_SCHEDULED_EVENT_USER_ADD, GUILD_SCHEDULED_EVENT_USER_REMOVE
///
GuildScheduledEvents = 1 << 16,
+
+ ///
+ /// This intent includes AUTO_MODERATION_RULE_CREATE, AUTO_MODERATION_RULE_UPDATE, AUTO_MODERATION_RULE_DELETE
+ ///
+ AutoModerationConfiguration = 1 << 20,
+
+ ///
+ /// This intent includes AUTO_MODERATION_ACTION_EXECUTION
+ ///
+ AutoModerationActionExecution = 1 << 21,
+
///
/// This intent includes all but and
/// which are privileged and must be enabled in the Developer Portal.
///
AllUnprivileged = Guilds | GuildBans | GuildEmojis | GuildIntegrations | GuildWebhooks | GuildInvites |
GuildVoiceStates | GuildMessages | GuildMessageReactions | GuildMessageTyping | DirectMessages |
- DirectMessageReactions | DirectMessageTyping | GuildScheduledEvents,
+ DirectMessageReactions | DirectMessageTyping | GuildScheduledEvents | AutoModerationConfiguration |
+ AutoModerationActionExecution,
///
/// This intent includes all of them, including privileged ones.
///
diff --git a/src/Discord.Net.WebSocket/API/Gateway/AutoModActionExecutedEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/AutoModActionExecutedEvent.cs
new file mode 100644
index 000000000..44d2834d3
--- /dev/null
+++ b/src/Discord.Net.WebSocket/API/Gateway/AutoModActionExecutedEvent.cs
@@ -0,0 +1,38 @@
+using Newtonsoft.Json;
+namespace Discord.API.Gateway;
+
+internal class AutoModActionExecutedEvent
+{
+ [JsonProperty("guild_id")]
+ public ulong GuildId { get; set; }
+
+ [JsonProperty("action")]
+ public Discord.API.AutoModAction Action { get; set; }
+
+ [JsonProperty("rule_id")]
+ public ulong RuleId { get; set; }
+
+ [JsonProperty("rule_trigger_type")]
+ public AutoModTriggerType TriggerType { get; set; }
+
+ [JsonProperty("user_id")]
+ public ulong UserId { get; set; }
+
+ [JsonProperty("channel_id")]
+ public Optional ChannelId { get; set; }
+
+ [JsonProperty("message_id")]
+ public Optional MessageId { get; set; }
+
+ [JsonProperty("alert_system_message_id")]
+ public Optional AlertSystemMessageId { get; set; }
+
+ [JsonProperty("content")]
+ public string Content { get; set; }
+
+ [JsonProperty("matched_keyword")]
+ public Optional MatchedKeyword { get; set; }
+
+ [JsonProperty("matched_content")]
+ public Optional MatchedContent { get; set; }
+}
diff --git a/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs b/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs
index ffa075396..815703867 100644
--- a/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs
+++ b/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs
@@ -902,14 +902,27 @@ namespace Discord.WebSocket
}
internal readonly AsyncEvent> _autoModRuleCreated = new AsyncEvent>();
- public event Func AutoModRuleDelted
+ public event Func, SocketAutoModRule, Task> AutoModRuleUpdated
+ {
+ add => _autoModRuleUpdated.Add(value);
+ remove => _autoModRuleUpdated.Remove(value);
+ }
+ internal readonly AsyncEvent, SocketAutoModRule, Task>> _autoModRuleUpdated = new ();
+
+ public event Func AutoModRuleDeleted
{
add => _autoModRuleDeleted.Add(value);
remove => _autoModRuleDeleted.Remove(value);
}
internal readonly AsyncEvent> _autoModRuleDeleted = new AsyncEvent>();
- //public event Func
+ public event Func AutoModActionExecuted
+ {
+ add => _autoModActionExecuted.Add(value);
+ remove => _autoModActionExecuted.Remove(value);
+ }
+ internal readonly AsyncEvent> _autoModActionExecuted = new ();
+
#endregion
}
}
diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs
index 096eaf157..63f4ef221 100644
--- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs
+++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs
@@ -6,8 +6,10 @@ using Discord.Net.Udp;
using Discord.Net.WebSockets;
using Discord.Rest;
using Discord.Utils;
+
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
+
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
@@ -16,6 +18,7 @@ using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
+
using GameModel = Discord.API.Game;
namespace Discord.WebSocket
@@ -333,7 +336,8 @@ namespace Discord.WebSocket
await heartbeatTask.ConfigureAwait(false);
_heartbeatTask = null;
- while (_heartbeatTimes.TryDequeue(out _)) { }
+ while (_heartbeatTimes.TryDequeue(out _))
+ { }
_lastMessageTime = 0;
await _gatewayLogger.DebugAsync("Waiting for guild downloader").ConfigureAwait(false);
@@ -344,7 +348,8 @@ namespace Discord.WebSocket
//Clear large guild queue
await _gatewayLogger.DebugAsync("Clearing large guild queue").ConfigureAwait(false);
- while (_largeGuilds.TryDequeue(out _)) { }
+ while (_largeGuilds.TryDequeue(out _))
+ { }
//Raise virtual GUILD_UNAVAILABLEs
await _gatewayLogger.DebugAsync("Raising virtual GuildUnavailables").ConfigureAwait(false);
@@ -461,7 +466,7 @@ namespace Discord.WebSocket
{
var commands = (await ApiClient.GetGlobalApplicationCommandsAsync(withLocalizations, locale, options)).Select(x => SocketApplicationCommand.Create(this, x));
- foreach(var command in commands)
+ foreach (var command in commands)
{
State.AddCommand(command);
}
@@ -490,7 +495,7 @@ namespace Discord.WebSocket
//Purge our previous commands
State.PurgeCommands(x => x.IsGlobalCommand);
- foreach(var entity in entities)
+ foreach (var entity in entities)
{
State.AddCommand(entity);
}
@@ -531,7 +536,7 @@ namespace Discord.WebSocket
if (sticker != null)
return sticker;
- foreach(var guild in Guilds)
+ foreach (var guild in Guilds)
{
sticker = await guild.GetStickerAsync(id, CacheMode.CacheOnly).ConfigureAwait(false);
@@ -544,7 +549,7 @@ namespace Discord.WebSocket
var model = await ApiClient.GetStickerAsync(id, options).ConfigureAwait(false);
- if(model == null)
+ if (model == null)
return null;
@@ -750,7 +755,7 @@ namespace Discord.WebSocket
await _gatewayLogger.WarningAsync("You're using the GuildPresences intent without listening to the PresenceUpdate event, consider removing the intent from your config.").ConfigureAwait(false);
}
- if(!_gatewayIntents.HasFlag(GatewayIntents.GuildPresences) &&
+ if (!_gatewayIntents.HasFlag(GatewayIntents.GuildPresences) &&
((_shardedClient is null && _presenceUpdated.HasSubscribers) ||
(_shardedClient is not null && _shardedClient._presenceUpdated.HasSubscribers)))
{
@@ -767,7 +772,7 @@ namespace Discord.WebSocket
_guildScheduledEventUserAdd.HasSubscribers;
bool shardedClientHasGuildScheduledEventsSubscribers =
- _shardedClient is not null &&
+ _shardedClient is not null &&
(_shardedClient._guildScheduledEventCancelled.HasSubscribers ||
_shardedClient._guildScheduledEventUserRemove.HasSubscribers ||
_shardedClient._guildScheduledEventCompleted.HasSubscribers ||
@@ -783,7 +788,7 @@ namespace Discord.WebSocket
await _gatewayLogger.WarningAsync("You're using the GuildScheduledEvents gateway intent without listening to any events related to that intent, consider removing the intent from your config.").ConfigureAwait(false);
}
- if(!_gatewayIntents.HasFlag(GatewayIntents.GuildScheduledEvents) &&
+ if (!_gatewayIntents.HasFlag(GatewayIntents.GuildScheduledEvents) &&
((_shardedClient is null && hasGuildScheduledEventsSubscribers) ||
(_shardedClient is not null && shardedClientHasGuildScheduledEventsSubscribers)))
{
@@ -2238,12 +2243,12 @@ namespace Discord.WebSocket
await TimedInvokeAsync(_requestToSpeak, nameof(RequestToSpeak), stage, guildUser);
return;
}
- if(before.IsSuppressed && !after.IsSuppressed)
+ if (before.IsSuppressed && !after.IsSuppressed)
{
await TimedInvokeAsync(_speakerAdded, nameof(SpeakerAdded), stage, guildUser);
return;
}
- if(!before.IsSuppressed && after.IsSuppressed)
+ if (!before.IsSuppressed && after.IsSuppressed)
{
await TimedInvokeAsync(_speakerRemoved, nameof(SpeakerRemoved), stage, guildUser);
}
@@ -2349,7 +2354,7 @@ namespace Discord.WebSocket
case "INTERACTION_CREATE":
{
await _gatewayLogger.DebugAsync("Received Dispatch (INTERACTION_CREATE)").ConfigureAwait(false);
-
+
var data = (payload as JToken).ToObject(_serializer);
var guild = data.GuildId.IsSpecified ? GetGuild(data.GuildId.Value) : null;
@@ -2366,7 +2371,7 @@ namespace Discord.WebSocket
: State.GetOrAddUser(data.Member.Value.User.Id, (_) => SocketGlobalUser.Create(this, State, data.Member.Value.User));
SocketChannel channel = null;
- if(data.ChannelId.IsSpecified)
+ if (data.ChannelId.IsSpecified)
{
channel = State.GetChannel(data.ChannelId.Value);
@@ -2507,7 +2512,7 @@ namespace Discord.WebSocket
{
threadChannel.Update(State, data);
- if(data.ThreadMember.IsSpecified)
+ if (data.ThreadMember.IsSpecified)
threadChannel.AddOrUpdateThreadMember(data.ThreadMember.Value, guild.CurrentUser);
}
else
@@ -2570,7 +2575,7 @@ namespace Discord.WebSocket
var guild = State.GetGuild(data.GuildId.Value);
- if(guild == null)
+ if (guild == null)
{
await UnknownGuildAsync(type, data.GuildId.Value).ConfigureAwait(false);
return;
@@ -2591,17 +2596,17 @@ namespace Discord.WebSocket
var guild = State.GetGuild(data.GuildId);
- if(guild == null)
+ if (guild == null)
{
await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false);
return;
}
- foreach(var thread in data.Threads)
+ foreach (var thread in data.Threads)
{
var entity = guild.ThreadChannels.FirstOrDefault(x => x.Id == thread.Id);
- if(entity == null)
+ if (entity == null)
{
entity = (SocketThreadChannel)guild.AddChannel(State, thread);
}
@@ -2610,7 +2615,7 @@ namespace Discord.WebSocket
entity.Update(State, thread);
}
- foreach(var member in data.Members.Where(x => x.Id.Value == entity.Id))
+ foreach (var member in data.Members.Where(x => x.Id.Value == entity.Id))
{
var guildMember = guild.GetUser(member.Id.Value);
@@ -2653,7 +2658,7 @@ namespace Discord.WebSocket
var thread = (SocketThreadChannel)guild.GetChannel(data.Id);
- if(thread == null)
+ if (thread == null)
{
await UnknownChannelAsync(type, data.Id);
return;
@@ -2671,13 +2676,13 @@ namespace Discord.WebSocket
if (data.AddedMembers.IsSpecified)
{
List newThreadMembers = new List();
- foreach(var threadMember in data.AddedMembers.Value)
+ foreach (var threadMember in data.AddedMembers.Value)
{
SocketGuildUser guildMember;
guildMember = guild.GetUser(threadMember.UserId.Value);
- if(guildMember == null)
+ if (guildMember == null)
{
await UnknownGuildUserAsync("THREAD_MEMBERS_UPDATE", threadMember.UserId.Value, guild.Id);
}
@@ -2691,15 +2696,15 @@ namespace Discord.WebSocket
if (leftUsers != null)
{
- foreach(var threadUser in leftUsers)
+ foreach (var threadUser in leftUsers)
{
await TimedInvokeAsync(_threadMemberLeft, nameof(ThreadMemberLeft), threadUser).ConfigureAwait(false);
}
}
- if(joinUsers != null)
+ if (joinUsers != null)
{
- foreach(var threadUser in joinUsers)
+ foreach (var threadUser in joinUsers)
{
await TimedInvokeAsync(_threadMemberJoined, nameof(ThreadMemberJoined), threadUser).ConfigureAwait(false);
}
@@ -2718,7 +2723,7 @@ namespace Discord.WebSocket
var guild = State.GetGuild(data.GuildId);
- if(guild == null)
+ if (guild == null)
{
await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false);
return;
@@ -2726,7 +2731,7 @@ namespace Discord.WebSocket
var stageChannel = guild.GetStageChannel(data.ChannelId);
- if(stageChannel == null)
+ if (stageChannel == null)
{
await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false);
return;
@@ -2792,15 +2797,16 @@ namespace Discord.WebSocket
var after = guild.AddOrUpdateEvent(data);
- if((before != null ? before.Status != GuildScheduledEventStatus.Completed : true) && data.Status == GuildScheduledEventStatus.Completed)
+ if ((before != null ? before.Status != GuildScheduledEventStatus.Completed : true) && data.Status == GuildScheduledEventStatus.Completed)
{
await TimedInvokeAsync(_guildScheduledEventCompleted, nameof(GuildScheduledEventCompleted), after).ConfigureAwait(false);
}
- else if((before != null ? before.Status != GuildScheduledEventStatus.Active : false) && data.Status == GuildScheduledEventStatus.Active)
+ else if ((before != null ? before.Status != GuildScheduledEventStatus.Active : false) && data.Status == GuildScheduledEventStatus.Active)
{
await TimedInvokeAsync(_guildScheduledEventStarted, nameof(GuildScheduledEventStarted), after).ConfigureAwait(false);
}
- else await TimedInvokeAsync(_guildScheduledEventUpdated, nameof(GuildScheduledEventUpdated), beforeCacheable, after).ConfigureAwait(false);
+ else
+ await TimedInvokeAsync(_guildScheduledEventUpdated, nameof(GuildScheduledEventUpdated), beforeCacheable, after).ConfigureAwait(false);
}
break;
case "GUILD_SCHEDULED_EVENT_DELETE":
@@ -2830,7 +2836,7 @@ namespace Discord.WebSocket
var guild = State.GetGuild(data.GuildId);
- if(guild == null)
+ if (guild == null)
{
await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false);
return;
@@ -2899,9 +2905,76 @@ namespace Discord.WebSocket
}
break;
- case "AUTO_MODERATION_RULE_EXECUTE":
+ case "AUTO_MODERATION_ACTION_EXECUTION":
{
+ var data = (payload as JToken).ToObject(_serializer);
+
+ var guild = State.GetGuild(data.GuildId);
+ var action = new AutoModRuleAction(data.Action.Type,
+ data.Action.Metadata.IsSpecified
+ ? data.Action.Metadata.Value.ChannelId.IsSpecified
+ ? data.Action.Metadata.Value.ChannelId.Value
+ : null
+ : null,
+ data.Action.Metadata.IsSpecified
+ ? data.Action.Metadata.Value.DurationSeconds.IsSpecified
+ ? data.Action.Metadata.Value.DurationSeconds.Value
+ : null
+ : null);
+
+ var member = guild.GetUser(data.UserId);
+
+ var cacheableUser = new Cacheable(member,
+ data.UserId,
+ member is not null,
+ () => Task.FromResult((SocketGuildUser)null)
+ );
+
+ ISocketMessageChannel channel = null;
+ if (data.ChannelId.IsSpecified)
+ channel = GetChannel(data.ChannelId.Value) as ISocketMessageChannel;
+
+ var cacheableChannel = new Cacheable(channel,
+ data.ChannelId.GetValueOrDefault(0),
+ channel != null,
+ async () =>
+ {
+ if(data.ChannelId.IsSpecified)
+ return await GetChannelAsync(data.ChannelId.Value).ConfigureAwait(false) as ISocketMessageChannel;
+ return null;
+ });
+
+
+ var cachedMsg = channel?.GetCachedMessage(data.MessageId.GetValueOrDefault(0)) as IUserMessage;
+
+ var cacheableMessage = new Cacheable(cachedMsg,
+ data.MessageId.GetValueOrDefault(0),
+ cachedMsg is not null,
+ async () =>
+ {
+ if(data.MessageId.IsSpecified)
+ return (await channel.GetMessageAsync(data.MessageId.Value).ConfigureAwait(false)) as IUserMessage;
+ return null;
+ });
+ var rule = new Cacheable(null, data.RuleId, false, () => null);
+
+ var eventData = new AutoModActionExecutedData(
+ rule,
+ data.TriggerType,
+ cacheableUser,
+ cacheableChannel,
+ cacheableMessage,
+ data.AlertSystemMessageId.GetValueOrDefault(0),
+ data.Content,
+ data.MatchedContent.IsSpecified
+ ? data.MatchedContent.Value
+ : null,
+ data.MatchedKeyword.IsSpecified
+ ? data.MatchedKeyword.Value
+ : null);
+
+ await TimedInvokeAsync(_autoModActionExecuted, nameof(AutoModActionExecuted), guild, action, eventData);
}
break;
@@ -2930,7 +3003,8 @@ namespace Discord.WebSocket
#region Others
default:
- if(!SuppressUnknownDispatchWarnings) await _gatewayLogger.WarningAsync($"Unknown Dispatch ({type})").ConfigureAwait(false);
+ if (!SuppressUnknownDispatchWarnings)
+ await _gatewayLogger.WarningAsync($"Unknown Dispatch ({type})").ConfigureAwait(false);
break;
#endregion
}
diff --git a/src/Discord.Net.WebSocket/Entities/Guilds/AutoModActionExecutedData.cs b/src/Discord.Net.WebSocket/Entities/Guilds/AutoModActionExecutedData.cs
new file mode 100644
index 000000000..65f131fd0
--- /dev/null
+++ b/src/Discord.Net.WebSocket/Entities/Guilds/AutoModActionExecutedData.cs
@@ -0,0 +1,46 @@
+using Discord.Rest;
+
+namespace Discord.WebSocket;
+
+public class AutoModActionExecutedData
+{
+ public Cacheable Rule { get; }
+
+ public AutoModTriggerType TriggerType { get; }
+
+ public Cacheable User { get; }
+
+ public Cacheable Channel { get; }
+
+ public Cacheable Message { get; }
+
+ public ulong AlertMessageId { get; }
+
+ public string Content { get; }
+
+ public string MatchedContent { get; }
+
+ public string MatchedKeyword { get; }
+
+ internal AutoModActionExecutedData(Cacheable rule,
+ AutoModTriggerType triggerType,
+ Cacheable user,
+ Cacheable channel,
+ Cacheable message,
+ ulong alertMessageId,
+ string content,
+ string matchedContent,
+ string matchedKeyword
+ )
+ {
+ Rule = rule;
+ TriggerType = triggerType;
+ User = user;
+ Channel = channel;
+ Message = message;
+ AlertMessageId = alertMessageId;
+ Content = content;
+ MatchedContent = matchedContent;
+ MatchedKeyword = matchedKeyword;
+ }
+}