diff --git a/endpoints.go b/endpoints.go index f5822da64..b0215fceb 100644 --- a/endpoints.go +++ b/endpoints.go @@ -68,6 +68,9 @@ var ( EndpointUserConnections = func(uID string) string { return EndpointUsers + uID + "/connections" } EndpointGuild = func(gID string) string { return EndpointGuilds + gID } + EndpointGuildAutoModeration = func(gID string) string { return EndpointGuild(gID) + "/auto-moderation" } + EndpointGuildAutoModerationRules = func(gID string) string { return EndpointGuildAutoModeration(gID) + "/rules" } + EndpointGuildAutoModerationRule = func(gID, rID string) string { return EndpointGuildAutoModerationRules(gID) + "/" + rID } EndpointGuildThreads = func(gID string) string { return EndpointGuild(gID) + "/threads" } EndpointGuildActiveThreads = func(gID string) string { return EndpointGuildThreads(gID) + "/active" } EndpointGuildPreview = func(gID string) string { return EndpointGuilds + gID + "/preview" } diff --git a/eventhandlers.go b/eventhandlers.go index f2ec080c3..36beefdf4 100644 --- a/eventhandlers.go +++ b/eventhandlers.go @@ -8,6 +8,10 @@ package discordgo // EventTypes surrounded by __ are synthetic and are internal to DiscordGo. const ( applicationCommandPermissionsUpdateEventType = "APPLICATION_COMMAND_PERMISSIONS_UPDATE" + autoModerationActionExecutionEventType = "AUTO_MODERATION_ACTION_EXECUTION" + autoModerationRuleCreateEventType = "AUTO_MODERATION_RULE_CREATE" + autoModerationRuleDeleteEventType = "AUTO_MODERATION_RULE_DELETE" + autoModerationRuleUpdateEventType = "AUTO_MODERATION_RULE_UPDATE" channelCreateEventType = "CHANNEL_CREATE" channelDeleteEventType = "CHANNEL_DELETE" channelPinsUpdateEventType = "CHANNEL_PINS_UPDATE" @@ -91,6 +95,86 @@ func (eh applicationCommandPermissionsUpdateEventHandler) Handle(s *Session, i i } } +// autoModerationActionExecutionEventHandler is an event handler for AutoModerationActionExecution events. +type autoModerationActionExecutionEventHandler func(*Session, *AutoModerationActionExecution) + +// Type returns the event type for AutoModerationActionExecution events. +func (eh autoModerationActionExecutionEventHandler) Type() string { + return autoModerationActionExecutionEventType +} + +// New returns a new instance of AutoModerationActionExecution. +func (eh autoModerationActionExecutionEventHandler) New() interface{} { + return &AutoModerationActionExecution{} +} + +// Handle is the handler for AutoModerationActionExecution events. +func (eh autoModerationActionExecutionEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*AutoModerationActionExecution); ok { + eh(s, t) + } +} + +// autoModerationRuleCreateEventHandler is an event handler for AutoModerationRuleCreate events. +type autoModerationRuleCreateEventHandler func(*Session, *AutoModerationRuleCreate) + +// Type returns the event type for AutoModerationRuleCreate events. +func (eh autoModerationRuleCreateEventHandler) Type() string { + return autoModerationRuleCreateEventType +} + +// New returns a new instance of AutoModerationRuleCreate. +func (eh autoModerationRuleCreateEventHandler) New() interface{} { + return &AutoModerationRuleCreate{} +} + +// Handle is the handler for AutoModerationRuleCreate events. +func (eh autoModerationRuleCreateEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*AutoModerationRuleCreate); ok { + eh(s, t) + } +} + +// autoModerationRuleDeleteEventHandler is an event handler for AutoModerationRuleDelete events. +type autoModerationRuleDeleteEventHandler func(*Session, *AutoModerationRuleDelete) + +// Type returns the event type for AutoModerationRuleDelete events. +func (eh autoModerationRuleDeleteEventHandler) Type() string { + return autoModerationRuleDeleteEventType +} + +// New returns a new instance of AutoModerationRuleDelete. +func (eh autoModerationRuleDeleteEventHandler) New() interface{} { + return &AutoModerationRuleDelete{} +} + +// Handle is the handler for AutoModerationRuleDelete events. +func (eh autoModerationRuleDeleteEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*AutoModerationRuleDelete); ok { + eh(s, t) + } +} + +// autoModerationRuleUpdateEventHandler is an event handler for AutoModerationRuleUpdate events. +type autoModerationRuleUpdateEventHandler func(*Session, *AutoModerationRuleUpdate) + +// Type returns the event type for AutoModerationRuleUpdate events. +func (eh autoModerationRuleUpdateEventHandler) Type() string { + return autoModerationRuleUpdateEventType +} + +// New returns a new instance of AutoModerationRuleUpdate. +func (eh autoModerationRuleUpdateEventHandler) New() interface{} { + return &AutoModerationRuleUpdate{} +} + +// Handle is the handler for AutoModerationRuleUpdate events. +func (eh autoModerationRuleUpdateEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*AutoModerationRuleUpdate); ok { + eh(s, t) + } +} + // channelCreateEventHandler is an event handler for ChannelCreate events. type channelCreateEventHandler func(*Session, *ChannelCreate) @@ -1297,6 +1381,14 @@ func handlerForInterface(handler interface{}) EventHandler { return interfaceEventHandler(v) case func(*Session, *ApplicationCommandPermissionsUpdate): return applicationCommandPermissionsUpdateEventHandler(v) + case func(*Session, *AutoModerationActionExecution): + return autoModerationActionExecutionEventHandler(v) + case func(*Session, *AutoModerationRuleCreate): + return autoModerationRuleCreateEventHandler(v) + case func(*Session, *AutoModerationRuleDelete): + return autoModerationRuleDeleteEventHandler(v) + case func(*Session, *AutoModerationRuleUpdate): + return autoModerationRuleUpdateEventHandler(v) case func(*Session, *ChannelCreate): return channelCreateEventHandler(v) case func(*Session, *ChannelDelete): @@ -1426,6 +1518,10 @@ func handlerForInterface(handler interface{}) EventHandler { func init() { registerInterfaceProvider(applicationCommandPermissionsUpdateEventHandler(nil)) + registerInterfaceProvider(autoModerationActionExecutionEventHandler(nil)) + registerInterfaceProvider(autoModerationRuleCreateEventHandler(nil)) + registerInterfaceProvider(autoModerationRuleDeleteEventHandler(nil)) + registerInterfaceProvider(autoModerationRuleUpdateEventHandler(nil)) registerInterfaceProvider(channelCreateEventHandler(nil)) registerInterfaceProvider(channelDeleteEventHandler(nil)) registerInterfaceProvider(channelPinsUpdateEventHandler(nil)) diff --git a/events.go b/events.go index 17c78281f..5cb7622d7 100644 --- a/events.go +++ b/events.go @@ -406,3 +406,33 @@ type InviteDelete struct { type ApplicationCommandPermissionsUpdate struct { *GuildApplicationCommandPermissions } + +// AutoModerationRuleCreate is the data for an AutoModerationRuleCreate event. +type AutoModerationRuleCreate struct { + *AutoModerationRule +} + +// AutoModerationRuleUpdate is the data for an AutoModerationRuleUpdate event. +type AutoModerationRuleUpdate struct { + *AutoModerationRule +} + +// AutoModerationRuleDelete is the data for an AutoModerationRuleDelete event. +type AutoModerationRuleDelete struct { + *AutoModerationRule +} + +// AutoModerationActionExecution is the data for an AutoModerationActionExecution event. +type AutoModerationActionExecution struct { + GuildID string `json:"guild_id"` + Action AutoModerationAction `json:"action"` + RuleID string `json:"rule_id"` + RuleTriggerType AutoModerationRuleTriggerType `json:"rule_trigger_type"` + UserID string `json:"user_id"` + ChannelID string `json:"channel_id"` + MessageID string `json:"message_id"` + AlertSystemMessageID string `json:"alert_system_message_id"` + Content string `json:"content"` + MatchedKeyword string `json:"matched_keyword"` + MatchedContent string `json:"matched_content"` +} diff --git a/examples/auto_moderation/main.go b/examples/auto_moderation/main.go new file mode 100644 index 000000000..66fbbcbdc --- /dev/null +++ b/examples/auto_moderation/main.go @@ -0,0 +1,117 @@ +package main + +import ( + "flag" + "fmt" + "log" + "os" + "os/signal" + "sync" + + "github.com/bwmarrin/discordgo" +) + +// Command line flags +var ( + BotToken = flag.String("token", "", "Bot authorization token") + GuildID = flag.String("guild", "", "ID of the testing guild") + ChannelID = flag.String("channel", "", "ID of the testing channel") +) + +func init() { flag.Parse() } + +func main() { + session, _ := discordgo.New("Bot " + *BotToken) + session.Identify.Intents |= discordgo.IntentAutoModerationExecution + session.Identify.Intents |= discordgo.IntentMessageContent + + enabled := true + rule, err := session.AutoModerationRuleCreate(*GuildID, &discordgo.AutoModerationRule{ + Name: "Auto Moderation example", + EventType: discordgo.AutoModerationEventMessageSend, + TriggerType: discordgo.AutoModerationEventTriggerKeyword, + TriggerMetadata: &discordgo.AutoModerationTriggerMetadata{ + KeywordFilter: []string{"*cat*"}, + }, + + Enabled: &enabled, + Actions: []discordgo.AutoModerationAction{ + {Type: discordgo.AutoModerationRuleActionBlockMessage}, + }, + }) + if err != nil { + panic(err) + } + + fmt.Println("Successfully created the rule") + defer session.AutoModerationRuleDelete(*GuildID, rule.ID) + + session.AddHandlerOnce(func(s *discordgo.Session, e *discordgo.AutoModerationActionExecution) { + _, err = session.AutoModerationRuleEdit(*GuildID, rule.ID, &discordgo.AutoModerationRule{ + TriggerMetadata: &discordgo.AutoModerationTriggerMetadata{ + KeywordFilter: []string{"cat"}, + }, + Actions: []discordgo.AutoModerationAction{ + {Type: discordgo.AutoModerationRuleActionTimeout, Metadata: &discordgo.AutoModerationActionMetadata{Duration: 60}}, + {Type: discordgo.AutoModerationRuleActionSendAlertMessage, Metadata: &discordgo.AutoModerationActionMetadata{ + ChannelID: e.ChannelID, + }}, + }, + }) + if err != nil { + session.AutoModerationRuleDelete(*GuildID, rule.ID) + panic(err) + } + + s.ChannelMessageSend(e.ChannelID, "Congratulations! You have just triggered an auto moderation rule.\n"+ + "The current trigger can match anywhere in the word, so even if you write the trigger word as a part of another word, it will still match.\n"+ + "The rule has now been changed, now the trigger matches only in the full words.\n"+ + "Additionally, when you send a message, an alert will be sent to this channel and you will be **timed out** for a minute.\n") + + var counter int + var counterMutex sync.Mutex + session.AddHandler(func(s *discordgo.Session, e *discordgo.AutoModerationActionExecution) { + action := "unknown" + switch e.Action.Type { + case discordgo.AutoModerationRuleActionBlockMessage: + action = "block message" + case discordgo.AutoModerationRuleActionSendAlertMessage: + action = "send alert message into <#" + e.Action.Metadata.ChannelID + ">" + case discordgo.AutoModerationRuleActionTimeout: + action = "timeout" + } + + counterMutex.Lock() + counter++ + if counter == 1 { + counterMutex.Unlock() + s.ChannelMessageSend(e.ChannelID, "Nothing has changed, right? "+ + "Well, since separate gateway events are fired per each action (current is "+action+"), "+ + "you'll see a second message about an action pop up soon") + } else if counter == 2 { + counterMutex.Unlock() + s.ChannelMessageSend(e.ChannelID, "Now the second ("+action+") action got executed.") + s.ChannelMessageSend(e.ChannelID, "And... you've made it! That's the end of the example.\n"+ + "For more information about the automod and how to use it, "+ + "you can visit the official Discord docs: https://discord.dev/resources/auto-moderation or ask in our server: https://discord.gg/6dzbuDpSWY", + ) + + session.Close() + session.AutoModerationRuleDelete(*GuildID, rule.ID) + os.Exit(0) + } + }) + }) + + err = session.Open() + if err != nil { + log.Fatalf("Cannot open the session: %v", err) + } + defer session.Close() + + stop := make(chan os.Signal, 1) + signal.Notify(stop, os.Interrupt) + <-stop + log.Println("Graceful shutdown") + +} diff --git a/restapi.go b/restapi.go index 8c700bed8..72daaa702 100644 --- a/restapi.go +++ b/restapi.go @@ -3111,3 +3111,80 @@ func (s *Session) GuildScheduledEventUsers(guildID, eventID string, limit int, w err = unmarshal(body, &st) return } + +// ---------------------------------------------------------------------- +// Functions specific to auto moderation +// ---------------------------------------------------------------------- + +// AutoModerationRules returns a list of auto moderation rules. +// guildID : ID of the guild +func (s *Session) AutoModerationRules(guildID string) (st []*AutoModerationRule, err error) { + endpoint := EndpointGuildAutoModerationRules(guildID) + + var body []byte + body, err = s.RequestWithBucketID("GET", endpoint, nil, endpoint) + if err != nil { + return + } + + err = unmarshal(body, &st) + return +} + +// AutoModerationRule returns an auto moderation rule. +// guildID : ID of the guild +// ruleID : ID of the auto moderation rule +func (s *Session) AutoModerationRule(guildID, ruleID string) (st *AutoModerationRule, err error) { + endpoint := EndpointGuildAutoModerationRule(guildID, ruleID) + + var body []byte + body, err = s.RequestWithBucketID("GET", endpoint, nil, endpoint) + if err != nil { + return + } + + err = unmarshal(body, &st) + return +} + +// AutoModerationRuleCreate creates an auto moderation rule with the given data and returns it. +// guildID : ID of the guild +// rule : Rule data +func (s *Session) AutoModerationRuleCreate(guildID string, rule *AutoModerationRule) (st *AutoModerationRule, err error) { + endpoint := EndpointGuildAutoModerationRules(guildID) + + var body []byte + body, err = s.RequestWithBucketID("POST", endpoint, rule, endpoint) + if err != nil { + return + } + + err = unmarshal(body, &st) + return +} + +// AutoModerationRuleEdit edits and returns the updated auto moderation rule. +// guildID : ID of the guild +// ruleID : ID of the auto moderation rule +// rule : New rule data +func (s *Session) AutoModerationRuleEdit(guildID, ruleID string, rule *AutoModerationRule) (st *AutoModerationRule, err error) { + endpoint := EndpointGuildAutoModerationRule(guildID, ruleID) + + var body []byte + body, err = s.RequestWithBucketID("PATCH", endpoint, rule, endpoint) + if err != nil { + return + } + + err = unmarshal(body, &st) + return +} + +// AutoModerationRuleDelete deletes an auto moderation rule. +// guildID : ID of the guild +// ruleID : ID of the auto moderation rule +func (s *Session) AutoModerationRuleDelete(guildID, ruleID string) (err error) { + endpoint := EndpointGuildAutoModerationRule(guildID, ruleID) + _, err = s.RequestWithBucketID("DELETE", endpoint, nil, endpoint) + return +} diff --git a/structs.go b/structs.go index 268933d6c..8631feced 100644 --- a/structs.go +++ b/structs.go @@ -1303,6 +1303,88 @@ type GuildBan struct { User *User `json:"user"` } +// AutoModerationRule stores data for an auto moderation rule. +type AutoModerationRule struct { + ID string `json:"id,omitempty"` + GuildID string `json:"guild_id,omitempty"` + Name string `json:"name,omitempty"` + CreatorID string `json:"creator_id,omitempty"` + EventType AutoModerationRuleEventType `json:"event_type,omitempty"` + TriggerType AutoModerationRuleTriggerType `json:"trigger_type,omitempty"` + TriggerMetadata *AutoModerationTriggerMetadata `json:"trigger_metadata,omitempty"` + Actions []AutoModerationAction `json:"actions,omitempty"` + Enabled *bool `json:"enabled,omitempty"` + ExemptRoles *[]string `json:"exempt_roles,omitempty"` + ExemptChannels *[]string `json:"exempt_channels,omitempty"` +} + +// AutoModerationRuleEventType indicates in what event context a rule should be checked. +type AutoModerationRuleEventType int + +// Auto moderation rule event types. +const ( + // AutoModerationEventMessageSend is checked when a member sends or edits a message in the guild + AutoModerationEventMessageSend AutoModerationRuleEventType = 1 +) + +// AutoModerationRuleTriggerType represents the type of content which can trigger the rule. +type AutoModerationRuleTriggerType int + +// Auto moderation rule trigger types. +const ( + AutoModerationEventTriggerKeyword AutoModerationRuleTriggerType = 1 + AutoModerationEventTriggerHarmfulLink AutoModerationRuleTriggerType = 2 + AutoModerationEventTriggerSpam AutoModerationRuleTriggerType = 3 + AutoModerationEventTriggerKeywordPreset AutoModerationRuleTriggerType = 4 +) + +// AutoModerationKeywordPreset represents an internally pre-defined wordset. +type AutoModerationKeywordPreset uint + +// Auto moderation keyword presets. +const ( + AutoModerationKeywordPresetProfanity AutoModerationKeywordPreset = 1 + AutoModerationKeywordPresetSexualContent AutoModerationKeywordPreset = 2 + AutoModerationKeywordPresetSlurs AutoModerationKeywordPreset = 3 +) + +// AutoModerationTriggerMetadata represents additional metadata used to determine whether rule should be triggered. +type AutoModerationTriggerMetadata struct { + // Substrings which will be searched for in content. + // NOTE: should be only used with keyword trigger type. + KeywordFilter []string `json:"keyword_filter,omitempty"` + // Internally pre-defined wordsets which will be searched for in content. + // NOTE: should be only used with keyword preset trigger type. + Presets []AutoModerationKeywordPreset `json:"presets,omitempty"` +} + +// AutoModerationActionType represents an action which will execute whenever a rule is triggered. +type AutoModerationActionType int + +// Auto moderation actions types. +const ( + AutoModerationRuleActionBlockMessage AutoModerationActionType = 1 + AutoModerationRuleActionSendAlertMessage AutoModerationActionType = 2 + AutoModerationRuleActionTimeout AutoModerationActionType = 3 +) + +// AutoModerationActionMetadata represents additional metadata needed during execution for a specific action type. +type AutoModerationActionMetadata struct { + // Channel to which user content should be logged. + // NOTE: should be only used with send alert message action type. + ChannelID string `json:"channel_id,omitempty"` + + // Timeout duration in seconds (maximum of 2419200 - 4 weeks). + // NOTE: should be only used with timeout action type. + Duration int `json:"duration_seconds,omitempty"` +} + +// AutoModerationAction stores data for an auto moderation action. +type AutoModerationAction struct { + Type AutoModerationActionType `json:"type"` + Metadata *AutoModerationActionMetadata `json:"metadata,omitempty"` +} + // A GuildEmbed stores data for a guild embed. type GuildEmbed struct { Enabled bool `json:"enabled"` @@ -2061,23 +2143,25 @@ type Intent int // Constants for the different bit offsets of intents const ( - IntentGuilds Intent = 1 << 0 - IntentGuildMembers Intent = 1 << 1 - IntentGuildBans Intent = 1 << 2 - IntentGuildEmojis Intent = 1 << 3 - IntentGuildIntegrations Intent = 1 << 4 - IntentGuildWebhooks Intent = 1 << 5 - IntentGuildInvites Intent = 1 << 6 - IntentGuildVoiceStates Intent = 1 << 7 - IntentGuildPresences Intent = 1 << 8 - IntentGuildMessages Intent = 1 << 9 - IntentGuildMessageReactions Intent = 1 << 10 - IntentGuildMessageTyping Intent = 1 << 11 - IntentDirectMessages Intent = 1 << 12 - IntentDirectMessageReactions Intent = 1 << 13 - IntentDirectMessageTyping Intent = 1 << 14 - IntentMessageContent Intent = 1 << 15 - IntentGuildScheduledEvents Intent = 1 << 16 + IntentGuilds Intent = 1 << 0 + IntentGuildMembers Intent = 1 << 1 + IntentGuildBans Intent = 1 << 2 + IntentGuildEmojis Intent = 1 << 3 + IntentGuildIntegrations Intent = 1 << 4 + IntentGuildWebhooks Intent = 1 << 5 + IntentGuildInvites Intent = 1 << 6 + IntentGuildVoiceStates Intent = 1 << 7 + IntentGuildPresences Intent = 1 << 8 + IntentGuildMessages Intent = 1 << 9 + IntentGuildMessageReactions Intent = 1 << 10 + IntentGuildMessageTyping Intent = 1 << 11 + IntentDirectMessages Intent = 1 << 12 + IntentDirectMessageReactions Intent = 1 << 13 + IntentDirectMessageTyping Intent = 1 << 14 + IntentMessageContent Intent = 1 << 15 + IntentGuildScheduledEvents Intent = 1 << 16 + IntentAutoModerationConfiguration Intent = 1 << 20 + IntentAutoModerationExecution Intent = 1 << 21 // TODO: remove when compatibility is not needed @@ -2112,7 +2196,9 @@ const ( IntentDirectMessages | IntentDirectMessageReactions | IntentDirectMessageTyping | - IntentGuildScheduledEvents + IntentGuildScheduledEvents | + IntentAutoModerationConfiguration | + IntentAutoModerationExecution IntentsAll = IntentsAllWithoutPrivileged | IntentGuildMembers |