Auto moderation (#1201)

* feat: auto moderation

* feat(examples/automod): add message content intent

* style(examples/automod): newline between sections

* feat(AutoModerationActionExecution): add user id

Add user_id field to AutoModerationActionExecution event.

* refactor(events): remove todos

Remove TODO comments for AutoModerationRuleUpdate and AutoModerationRuleDelete.

* feat(AutoModerationEventMessageSend): doc comment

Add documentation comment to AutoModerationEventMessageSend constant.
This commit is contained in:
Fedor Lapshin 2022-07-03 21:51:15 +03:00 committed by GitHub
parent f697ccae8c
commit 4e021d9140
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 427 additions and 18 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1309,6 +1309,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"`
@ -2067,23 +2149,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
@ -2118,7 +2202,9 @@ const (
IntentDirectMessages |
IntentDirectMessageReactions |
IntentDirectMessageTyping |
IntentGuildScheduledEvents
IntentGuildScheduledEvents |
IntentAutoModerationConfiguration |
IntentAutoModerationExecution
IntentsAll = IntentsAllWithoutPrivileged |
IntentGuildMembers |