Documentation ¶
Index ¶
- Constants
- Variables
- func AddMemberMuteRole(config *Config, id int64, currentRoles []int64) (removedRoles []int64, err error)
- func BanUser(config *Config, guildID int64, channel *dstate.ChannelState, ...) error
- func BanUserWithDuration(config *Config, guildID int64, channel *dstate.ChannelState, ...) error
- func CreateLogs(guildID, channelID int64, user *discordgo.User) string
- func CreateModlogEmbed(config *Config, author *discordgo.User, action ModlogAction, ...) error
- func DeleteMessages(guildID, channelID int64, filterUser int64, deleteNum, fetchNum int) (int, error)
- func FindAuditLogEntry(guildID int64, typ discordgo.AuditLogAction, targetUser int64, ...) (author *discordgo.User, entry *discordgo.AuditLogEntry)
- func FindRole(gs *dstate.GuildSet, roleS string) *discordgo.Role
- func GenericCmdResp(action ModlogAction, target *discordgo.User, duration time.Duration, ...) string
- func HandleChannelCreateUpdate(evt *eventsystem.EventData) (retry bool, err error)
- func HandleClearServerWarnings(w http.ResponseWriter, r *http.Request) (web.TemplateData, error)
- func HandleGuildBanAddRemove(evt *eventsystem.EventData)
- func HandleGuildCreate(evt *eventsystem.EventData)
- func HandleGuildMemberRemove(evt *eventsystem.EventData) (retry bool, err error)
- func HandleGuildMemberTimeoutChange(evt *eventsystem.EventData) (retry bool, err error)
- func HandleGuildMemberUpdate(evt *eventsystem.EventData) (retry bool, err error)
- func HandleMemberJoin(evt *eventsystem.EventData) (retry bool, err error)
- func HandleModeration(w http.ResponseWriter, r *http.Request) (web.TemplateData, error)
- func HandlePostModeration(w http.ResponseWriter, r *http.Request) (web.TemplateData, error)
- func HandleRefreshMuteOverrides(evt *pubsub.Event)
- func HandleRefreshMuteOverridesCreateRole(evt *pubsub.Event)
- func KickUser(config *Config, guildID int64, channel *dstate.ChannelState, ...) error
- func LockMemberMuteMW(next eventsystem.HandlerFunc) eventsystem.HandlerFunc
- func LockMute(uID int64)
- func MBaseCmdSecond(cmdData *dcmd.Data, reason string, reasonArgOptional bool, neededPerm int64, ...) (oreason string, err error)
- func MuteUnmuteUser(config *Config, mute bool, guildID int64, channel *dstate.ChannelState, ...) error
- func PaginateWarnings(parsed *dcmd.Data) ...
- func RedisKeyBannedUser(guildID, userID int64) string
- func RedisKeyLockedMute(guildID, userID int64) string
- func RedisKeyMutedUser(guildID, userID int64) string
- func RedisKeyUnbannedUser(guildID, userID int64) string
- func RefreshMuteOverrideForChannel(config *Config, channel dstate.ChannelState)
- func RefreshMuteOverrides(guildID int64, createRole bool)
- func RegisterPlugin()
- func RemoveMemberMuteRole(config *Config, id int64, currentRoles []int64, mute *models.MutedUser) (err error)
- func RemoveTimeout(config *Config, guildID int64, author *discordgo.User, reason string, ...) error
- func SafeArgString(data *dcmd.Data, arg int) string
- func SaveConfig(config *Config) error
- func TimeoutUser(config *Config, guildID int64, channel *dstate.ChannelState, ...) error
- func UnbanUser(config *Config, guildID int64, author *discordgo.User, reason string, ...) (bool, error)
- func UnlockMute(uID int64)
- func WarnUser(config *Config, guildID int64, channel *dstate.ChannelState, ...) error
- type CombinedANDFilter
- type Config
- type ContextKey
- type IgnorePinnedMessagesFilter
- type MessageAgeFilter
- type MessageAuthorFilter
- type MessageFilter
- type MessageIDFilter
- type MessagesWithAttachmentsFilter
- type ModlogAction
- type Plugin
- func (p *Plugin) AddCommands()
- func (p *Plugin) AllFeatureFlags() []string
- func (p *Plugin) BotInit()
- func (p *Plugin) InitWeb()
- func (p *Plugin) LoadServerHomeWidget(w http.ResponseWriter, r *http.Request) (web.TemplateData, error)
- func (p *Plugin) PluginInfo() *common.PluginInfo
- func (p *Plugin) ShardMigrationReceive(evt dshardorchestrator.EventType, data interface{})
- func (p *Plugin) UpdateFeatureFlags(guildID int64) ([]string, error)
- type Punishment
- type RegExpFilter
- type ScheduledUnbanData
- type ScheduledUnmuteData
- type TemplatesWarning
- type WarnRankEntry
Constants ¶
const ( ActionMuted = "Muted" ActionUnMuted = "Unmuted" ActionKicked = "Kicked" ActionBanned = "Banned" ActionUnbanned = "Unbanned" ActionWarned = "Warned" )
const DefaultDMMessage = "You have been {{.ModAction}}\n**Reason:** {{.Reason}}"
const DefaultTimeoutDuration = 10 * time.Minute
const (
ErrNoMuteRole = errors.Sentinel("No mute role")
)
const MaxTimeOutDuration = 40320 * time.Minute
const MinTimeOutDuration = time.Minute
const MuteDeniedChannelPerms = discordgo.PermissionSendMessages | discordgo.PermissionVoiceSpeak | discordgo.PermissionUsePublicThreads | discordgo.PermissionUsePrivateThreads | discordgo.PermissionSendMessagesInThreads
Variables ¶
var ( MAMute = ModlogAction{Prefix: "Muted", Emoji: "🔇", Color: 0x57728e} MAUnmute = ModlogAction{Prefix: "Unmuted", Emoji: "🔊", Color: 0x62c65f} MAKick = ModlogAction{Prefix: "Kicked", Emoji: "👢", Color: 0xf2a013} MABanned = ModlogAction{Prefix: "Banned", Emoji: "🔨", Color: 0xd64848} MAUnbanned = ModlogAction{Prefix: "Unbanned", Emoji: "🔓", Color: 0x62c65f} MAWarned = ModlogAction{Prefix: "Warned", Emoji: "⚠", Color: 0xfca253} MATimeoutAdded = ModlogAction{Prefix: "Timed out", Emoji: "⏱", Color: 0x9b59b6} MATimeoutRemoved = ModlogAction{Prefix: "Timeout removed from", Emoji: "⏱", Color: 0x9b59b6} MAGiveRole = ModlogAction{Prefix: "", Emoji: "➕", Color: 0x53fcf9} MARemoveRole = ModlogAction{Prefix: "", Emoji: "➖", Color: 0x53fcf9} MAClearWarnings = ModlogAction{Prefix: "Cleared warnings", Emoji: "👌", Color: 0x62c65f} )
var ActionMap = map[string]string{ MAMute.Prefix: "Mute DM", MAUnmute.Prefix: "Unmute DM", MAKick.Prefix: "Kick DM", MABanned.Prefix: "Ban DM", MAWarned.Prefix: "Warn DM", MATimeoutAdded.Prefix: "Timeout DM", }
var DBSchemas = []string{`
CREATE TABLE IF NOT EXISTS moderation_configs (
guild_id BIGINT PRIMARY KEY,
created_at TIMESTAMP WITH TIME ZONE NOT NULL,
updated_at TIMESTAMP WITH TIME ZONE NOT NULL,
-- Many of the following columns should be non-nullable, but were originally
-- managed by gorm (which does not add NOT NULL constraints by default) so are
-- missing them. Unfortunately, it is unfeasible to retroactively fill missing
-- values with defaults and add the constraints as there are simply too many
-- rows in production.
-- For similar legacy reasons, many fields that should have type BIGINT are TEXT.
kick_enabled BOOLEAN,
kick_cmd_roles BIGINT[],
delete_messages_on_kick BOOLEAN,
kick_reason_optional BOOLEAN,
kick_message TEXT,
ban_enabled BOOLEAN,
ban_cmd_roles BIGINT[],
ban_reason_optional BOOLEAN,
ban_message TEXT,
default_ban_delete_days BIGINT DEFAULT 1,
timeout_enabled BOOLEAN,
timeout_cmd_roles BIGINT[],
timeout_reason_optional BOOLEAN,
timeout_remove_reason_optional BOOLEAN,
timeout_message TEXT,
default_timeout_duration BIGINT DEFAULT 10,
mute_enabled BOOLEAN,
mute_cmd_roles BIGINT[],
mute_role TEXT,
mute_disallow_reaction_add BOOLEAN,
mute_reason_optional BOOLEAN,
unmute_reason_optional BOOLEAN,
mute_manage_role BOOLEAN,
mute_remove_roles BIGINT[],
mute_ignore_channels BIGINT[],
mute_message TEXT,
unmute_message TEXT,
default_mute_duration BIGINT DEFAULT 10,
warn_commands_enabled BOOLEAN,
warn_cmd_roles BIGINT[],
warn_include_channel_logs BOOLEAN,
warn_send_to_modlog BOOLEAN,
warn_message TEXT,
clean_enabled BOOLEAN,
report_enabled BOOLEAN,
action_channel TEXT,
report_channel TEXT,
error_channel TEXT,
log_unbans BOOLEAN,
log_bans BOOLEAN,
log_kicks BOOLEAN DEFAULT TRUE,
log_timeouts BOOLEAN,
give_role_cmd_enabled BOOLEAN,
give_role_cmd_modlog BOOLEAN,
give_role_cmd_roles BIGINT[]
);
`, `
-- Tables created with gorm have missing NOT NULL constraints for created_at and
-- updated_at columns; since these columns are never null in existing rows, we can
-- retraoctively add the constraints without needing to update any data.
ALTER TABLE moderation_configs ALTER COLUMN created_at SET NOT NULL;
`, `
ALTER TABLE moderation_configs ALTER COLUMN updated_at SET NOT NULL;
`, `
CREATE TABLE IF NOT EXISTS moderation_warnings (
id SERIAL PRIMARY KEY,
created_at TIMESTAMP WITH TIME ZONE NOT NULL,
updated_at TIMESTAMP WITH TIME ZONE NOT NULL,
guild_id BIGINT NOT NULL,
user_id TEXT NOT NULL, -- text instead of bigint for legacy compatibility
author_id TEXT NOT NULL,
author_username_discrim TEXT NOT NULL,
message TEXT NOT NULL,
logs_link TEXT
);
`, `
CREATE INDEX IF NOT EXISTS idx_moderation_warnings_guild_id ON moderation_warnings(guild_id);
`, `
-- Similar to moderation_warnings.{created_at,updated_at}, there are a number of
-- fields that are never null in existing data but do not have the proper NOT NULL
-- constraints if they were created with gorm. Add them in.
ALTER TABLE moderation_warnings ALTER COLUMN created_at SET NOT NULL;
`, `
ALTER TABLE moderation_warnings ALTER COLUMN updated_at SET NOT NULL;
`, `
ALTER TABLE moderation_warnings ALTER COLUMN guild_id SET NOT NULL;
`, `
ALTER TABLE moderation_warnings ALTER COLUMN user_id SET NOT NULL;
`, `
ALTER TABLE moderation_warnings ALTER COLUMN author_id SET NOT NULL;
`, `
ALTER TABLE moderation_warnings ALTER COLUMN author_username_discrim SET NOT NULL;
`, `
ALTER TABLE moderation_warnings ALTER COLUMN message SET NOT NULL;
`, `
CREATE TABLE IF NOT EXISTS muted_users (
id SERIAL PRIMARY KEY,
created_at TIMESTAMP WITH TIME ZONE NOT NULL,
updated_at TIMESTAMP WITH TIME ZONE NOT NULL,
expires_at TIMESTAMP WITH TIME ZONE,
guild_id BIGINT NOT NULL,
user_id BIGINT NOT NULL,
author_id BIGINT NOT NULL,
reason TEXT NOT NULL,
removed_roles BIGINT[]
);
`, `
ALTER TABLE muted_users ALTER COLUMN created_at SET NOT NULL;
`, `
ALTER TABLE muted_users ALTER COLUMN updated_at SET NOT NULL;
`, `
ALTER TABLE muted_users ALTER COLUMN guild_id SET NOT NULL;
`, `
ALTER TABLE muted_users ALTER COLUMN user_id SET NOT NULL;
`, `
ALTER TABLE muted_users ALTER COLUMN author_id SET NOT NULL;
`, `
ALTER TABLE muted_users ALTER COLUMN reason SET NOT NULL;
`}
var (
ErrFailedPerms = errors.New("Failed retrieving perms")
)
var ModerationCommands = []*commands.YAGCommand{ { CustomEnabled: true, CmdCategory: commands.CategoryModeration, Name: "Ban", Aliases: []string{"banid"}, Description: "Bans a member, specify number of days of messages to delete with -ddays (0 to 7)", RequiredArgs: 1, Arguments: []*dcmd.ArgDef{ {Name: "User", Type: dcmd.UserID}, {Name: "Duration", Type: &commands.DurationArg{}, Default: time.Duration(0)}, {Name: "Reason", Type: dcmd.String}, }, ArgSwitches: []*dcmd.ArgDef{ {Name: "ddays", Help: "Number of days of messages to delete", Type: dcmd.Int}, }, RequiredDiscordPermsHelp: "BanMembers or ManageGuild", RequireBotPerms: [][]int64{{discordgo.PermissionAdministrator}, {discordgo.PermissionBanMembers}}, ArgumentCombos: [][]int{{0, 1, 2}, {0, 2, 1}, {0, 1}, {0, 2}, {0}}, SlashCommandEnabled: true, DefaultEnabled: false, IsResponseEphemeral: false, RunFunc: func(parsed *dcmd.Data) (interface{}, error) { if parsed.Context().Value(commands.CtxKeyExecutedByNestedCommandTemplate) == true { return nil, errors.New("cannot nest exec/execAdmin calls") } config, target, err := MBaseCmd(parsed, parsed.Args[0].Int64()) if err != nil { return nil, err } reason := SafeArgString(parsed, 2) reason, err = MBaseCmdSecond(parsed, reason, config.BanReasonOptional, discordgo.PermissionBanMembers, config.BanCmdRoles, config.BanEnabled, true) if err != nil { return nil, err } if utf8.RuneCountInString(reason) > 470 { return "Error: Reason too long (can be max 470 characters).", nil } err = checkHierarchy(parsed, parsed.Args[0].Int64()) if err != nil { return nil, err } ddays := int(config.DefaultBanDeleteDays.Int64) if parsed.Switches["ddays"].Value != nil { ddays = parsed.Switches["ddays"].Int() } banDuration := parsed.Args[1].Value.(time.Duration) var msg *discordgo.Message if parsed.TraditionalTriggerData != nil { msg = parsed.TraditionalTriggerData.Message } err = BanUserWithDuration(config, parsed.GuildData.GS.ID, parsed.GuildData.CS, msg, parsed.Author, reason, target, banDuration, ddays, parsed.Context().Value(commands.CtxKeyExecutedByCommandTemplate) == true) if err != nil { return nil, err } return GenericCmdResp(MABanned, target, banDuration, true, false), nil }, }, { CustomEnabled: true, CmdCategory: commands.CategoryModeration, Name: "Unban", Aliases: []string{"unbanid"}, Description: "Unbans a user. Reason requirement is same as ban command setting.", RequiredArgs: 1, Arguments: []*dcmd.ArgDef{ {Name: "User", Type: dcmd.UserID}, {Name: "Reason", Type: dcmd.String}, }, RequiredDiscordPermsHelp: "BanMembers or ManageGuild", RequireBotPerms: [][]int64{{discordgo.PermissionAdministrator}, {discordgo.PermissionBanMembers}}, SlashCommandEnabled: true, DefaultEnabled: false, IsResponseEphemeral: false, RunFunc: func(parsed *dcmd.Data) (interface{}, error) { config, _, err := MBaseCmd(parsed, 0) if err != nil { return nil, err } reason := SafeArgString(parsed, 1) reason, err = MBaseCmdSecond(parsed, reason, config.BanReasonOptional, discordgo.PermissionBanMembers, config.BanCmdRoles, config.BanEnabled, true) if err != nil { return nil, err } targetID := parsed.Args[0].Int64() target := &discordgo.User{ Username: "unknown", Discriminator: "????", ID: targetID, } targetMem, _ := bot.GetMember(parsed.GuildData.GS.ID, targetID) if targetMem != nil { return "User is not banned!", nil } isNotBanned, err := UnbanUser(config, parsed.GuildData.GS.ID, parsed.Author, reason, target) if err != nil { return nil, err } if isNotBanned { return "User is not banned!", nil } return GenericCmdResp(MAUnbanned, target, 0, true, true), nil }, }, { CustomEnabled: true, CmdCategory: commands.CategoryModeration, Name: "Kick", Description: "Kicks a member", RequiredArgs: 1, Arguments: []*dcmd.ArgDef{ {Name: "User", Type: dcmd.UserID}, {Name: "Reason", Type: dcmd.String}, }, RequiredDiscordPermsHelp: "KickMembers or ManageGuild", ArgSwitches: []*dcmd.ArgDef{ {Name: "cl", Help: "Messages to delete", Type: &dcmd.IntArg{Min: 1, Max: 100}}, }, RequireBotPerms: [][]int64{{discordgo.PermissionAdministrator}, {discordgo.PermissionKickMembers}}, SlashCommandEnabled: true, IsResponseEphemeral: false, RunFunc: func(parsed *dcmd.Data) (interface{}, error) { if parsed.Context().Value(commands.CtxKeyExecutedByNestedCommandTemplate) == true { return nil, errors.New("cannot nest exec/execAdmin calls") } config, target, err := MBaseCmd(parsed, parsed.Args[0].Int64()) if err != nil { return nil, err } reason := SafeArgString(parsed, 1) reason, err = MBaseCmdSecond(parsed, reason, config.KickReasonOptional, discordgo.PermissionKickMembers, config.KickCmdRoles, config.KickEnabled, true) if err != nil { return nil, err } member, err := bot.GetMember(parsed.GuildData.GS.ID, target.ID) if err != nil || member == nil { return "Member not found", err } if utf8.RuneCountInString(reason) > 470 { return "Error: Reason too long (can be max 470 characters).", nil } err = checkHierarchy(parsed, parsed.Args[0].Int64()) if err != nil { return nil, err } toDel := -1 if parsed.Switches["cl"].Value != nil { toDel = parsed.Switches["cl"].Int() } var msg *discordgo.Message if parsed.TraditionalTriggerData != nil { msg = parsed.TraditionalTriggerData.Message } err = KickUser(config, parsed.GuildData.GS.ID, parsed.GuildData.CS, msg, parsed.Author, reason, target, toDel, parsed.Context().Value(commands.CtxKeyExecutedByCommandTemplate) == true) if err != nil { return nil, err } return GenericCmdResp(MAKick, target, 0, true, true), nil }, }, { CustomEnabled: true, CmdCategory: commands.CategoryModeration, Name: "Mute", Description: "Mutes a member", Arguments: []*dcmd.ArgDef{ {Name: "User", Type: dcmd.UserID}, {Name: "Duration", Type: &commands.DurationArg{}}, {Name: "Reason", Type: dcmd.String}, }, RequiredDiscordPermsHelp: "KickMembers or ManageGuild", RequireBotPerms: [][]int64{{discordgo.PermissionAdministrator}, {discordgo.PermissionManageRoles}}, ArgumentCombos: [][]int{{0, 1, 2}, {0, 2, 1}, {0, 1}, {0, 2}, {0}}, SlashCommandEnabled: true, DefaultEnabled: false, IsResponseEphemeral: false, RunFunc: func(parsed *dcmd.Data) (interface{}, error) { if parsed.Context().Value(commands.CtxKeyExecutedByNestedCommandTemplate) == true { return nil, errors.New("cannot nest exec/execAdmin calls") } config, target, err := MBaseCmd(parsed, parsed.Args[0].Int64()) if err != nil { return nil, err } if config.MuteRole == 0 { return fmt.Sprintf("No mute role selected. Select one at <%s/moderation>", web.ManageServerURL(parsed.GuildData.GS.ID)), nil } reason := parsed.Args[2].Str() reason, err = MBaseCmdSecond(parsed, reason, config.MuteReasonOptional, discordgo.PermissionKickMembers, config.MuteCmdRoles, config.MuteEnabled, true) if err != nil { return nil, err } d := time.Duration(config.DefaultMuteDuration.Int64) * time.Minute if parsed.Args[1].Value != nil { d = parsed.Args[1].Value.(time.Duration) } if d > 0 && d < time.Minute { d = time.Minute } logger.Info(d.Seconds()) member, err := bot.GetMember(parsed.GuildData.GS.ID, target.ID) if err != nil || member == nil { return "Member not found", err } err = checkHierarchy(parsed, target.ID) if err != nil { return nil, err } var msg *discordgo.Message if parsed.TraditionalTriggerData != nil { msg = parsed.TraditionalTriggerData.Message } err = MuteUnmuteUser(config, true, parsed.GuildData.GS.ID, parsed.GuildData.CS, msg, parsed.Author, reason, member, int(d.Minutes()), parsed.Context().Value(commands.CtxKeyExecutedByCommandTemplate) == true) if err != nil { return nil, err } common.BotSession.GuildMemberMove(parsed.GuildData.GS.ID, target.ID, 0) return GenericCmdResp(MAMute, target, d, true, false), nil }, }, { CustomEnabled: true, CmdCategory: commands.CategoryModeration, Name: "Unmute", Description: "Unmutes a member", RequiredArgs: 1, Arguments: []*dcmd.ArgDef{ {Name: "User", Type: dcmd.UserID}, {Name: "Reason", Type: dcmd.String}, }, RequiredDiscordPermsHelp: "KickMembers or ManageGuild", RequireBotPerms: [][]int64{{discordgo.PermissionAdministrator}, {discordgo.PermissionManageRoles}}, SlashCommandEnabled: true, DefaultEnabled: false, IsResponseEphemeral: false, RunFunc: func(parsed *dcmd.Data) (interface{}, error) { if parsed.Context().Value(commands.CtxKeyExecutedByNestedCommandTemplate) == true { return nil, errors.New("cannot nest exec/execAdmin calls") } config, target, err := MBaseCmd(parsed, parsed.Args[0].Int64()) if err != nil { return nil, err } if config.MuteRole == 0 { return "No mute role set up, assign a mute role in the control panel", nil } reason := parsed.Args[1].Str() reason, err = MBaseCmdSecond(parsed, reason, config.UnmuteReasonOptional, discordgo.PermissionKickMembers, config.MuteCmdRoles, config.MuteEnabled, true) if err != nil { return nil, err } member, err := bot.GetMember(parsed.GuildData.GS.ID, target.ID) if err != nil || member == nil { return "Member not found", err } err = checkHierarchy(parsed, target.ID) if err != nil { return nil, err } var msg *discordgo.Message if parsed.TraditionalTriggerData != nil { msg = parsed.TraditionalTriggerData.Message } err = MuteUnmuteUser(config, false, parsed.GuildData.GS.ID, parsed.GuildData.CS, msg, parsed.Author, reason, member, 0, parsed.Context().Value(commands.CtxKeyExecutedByCommandTemplate) == true) if err != nil { return nil, err } return GenericCmdResp(MAUnmute, target, 0, false, true), nil }, }, { CustomEnabled: true, CmdCategory: commands.CategoryModeration, Name: "Timeout", Description: "Timeout a member", Aliases: []string{"to"}, Arguments: []*dcmd.ArgDef{ {Name: "User", Type: dcmd.UserID}, {Name: "Duration", Type: &commands.DurationArg{}}, {Name: "Reason", Type: dcmd.String}, }, RequiredDiscordPermsHelp: "TimeoutMembers/ModerateMembers or ManageGuild", RequireBotPerms: [][]int64{{discordgo.PermissionAdministrator}, {discordgo.PermissionModerateMembers}}, ArgumentCombos: [][]int{{0, 1, 2}, {0, 2, 1}, {0, 1}, {0, 2}, {0}}, SlashCommandEnabled: true, DefaultEnabled: false, IsResponseEphemeral: false, RunFunc: func(parsed *dcmd.Data) (interface{}, error) { if parsed.Context().Value(commands.CtxKeyExecutedByNestedCommandTemplate) == true { return nil, errors.New("cannot nest exec/execAdmin calls") } config, target, err := MBaseCmd(parsed, parsed.Args[0].Int64()) if err != nil { return nil, err } reason := parsed.Args[2].Str() reason, err = MBaseCmdSecond(parsed, reason, config.TimeoutReasonOptional, discordgo.PermissionModerateMembers, config.TimeoutCmdRoles, config.TimeoutEnabled, true) if err != nil { return nil, err } d := time.Duration(config.DefaultTimeoutDuration.Int64) * time.Minute if parsed.Args[1].Value != nil { d = parsed.Args[1].Value.(time.Duration) } if d < time.Minute { d = time.Minute } if d > MaxTimeOutDuration { return fmt.Sprintf("Error: Max duration of Timeouts can be %v days", (MaxTimeOutDuration.Hours() / 24)), nil } member, err := bot.GetMember(parsed.GuildData.GS.ID, target.ID) if err != nil || member == nil { return "Member not found", err } err = checkHierarchy(parsed, target.ID) if err != nil { return nil, err } var msg *discordgo.Message if parsed.TraditionalTriggerData != nil { msg = parsed.TraditionalTriggerData.Message } err = TimeoutUser(config, parsed.GuildData.GS.ID, parsed.GuildData.CS, msg, parsed.Author, reason, &member.User, d, parsed.Context().Value(commands.CtxKeyExecutedByCommandTemplate) == true) if err != nil { return nil, err } return GenericCmdResp(MATimeoutAdded, target, d, true, false), nil }, }, { CustomEnabled: true, CmdCategory: commands.CategoryModeration, Name: "RemoveTimeout", Aliases: []string{"untimeout", "cleartimeout", "deltimeout", "rto"}, Description: "Removes a member's timeout", RequiredArgs: 1, Arguments: []*dcmd.ArgDef{ {Name: "User", Type: dcmd.UserID}, {Name: "Reason", Type: dcmd.String}, }, RequiredDiscordPermsHelp: "TimeoutMember/ModerateMember or ManageGuild", RequireBotPerms: [][]int64{{discordgo.PermissionAdministrator}, {discordgo.PermissionModerateMembers}}, SlashCommandEnabled: true, DefaultEnabled: false, IsResponseEphemeral: false, RunFunc: func(parsed *dcmd.Data) (interface{}, error) { config, target, err := MBaseCmd(parsed, parsed.Args[0].Int64()) if err != nil { return nil, err } reason := parsed.Args[1].Str() reason, err = MBaseCmdSecond(parsed, reason, config.TimeoutReasonOptional, discordgo.PermissionModerateMembers, config.TimeoutCmdRoles, config.TimeoutEnabled, true) if err != nil { return nil, err } member, err := bot.GetMember(parsed.GuildData.GS.ID, target.ID) if err != nil || member == nil { return "Member not found", err } err = checkHierarchy(parsed, target.ID) if err != nil { return nil, err } memberTimeout := member.Member.CommunicationDisabledUntil if memberTimeout == nil || memberTimeout.Before(time.Now()) { return "Member is not timed out", nil } err = RemoveTimeout(config, parsed.GuildData.GS.ID, parsed.Author, reason, &member.User) if err != nil { return nil, err } return GenericCmdResp(MATimeoutRemoved, target, 0, false, true), nil }, }, { CustomEnabled: true, Cooldown: 5, CmdCategory: commands.CategoryModeration, Name: "Report", Description: "Reports a member to the server's staff", RequiredArgs: 2, Arguments: []*dcmd.ArgDef{ {Name: "User", Type: dcmd.UserID}, {Name: "Reason", Type: dcmd.String}, }, SlashCommandEnabled: true, DefaultEnabled: false, IsResponseEphemeral: false, RunFunc: func(parsed *dcmd.Data) (interface{}, error) { config, _, err := MBaseCmd(parsed, 0) if err != nil { return nil, err } _, err = MBaseCmdSecond(parsed, "", true, 0, nil, config.ReportEnabled, false) if err != nil { return nil, err } temp, err := bot.GetMember(parsed.GuildData.GS.ID, parsed.Args[0].Int64()) if err != nil || temp == nil { return nil, err } target := temp.User if target.ID == parsed.Author.ID { return "You can't report yourself, silly.", nil } logLink := CreateLogs(parsed.GuildData.GS.ID, parsed.GuildData.CS.ID, parsed.Author) channelID := config.ReportChannel if channelID == 0 { return "No report channel set up", nil } topContent := fmt.Sprintf("%s reported **%s (ID %d)**", parsed.Author.Mention(), target.String(), target.ID) embed := &discordgo.MessageEmbed{ Author: &discordgo.MessageEmbedAuthor{ Name: fmt.Sprintf("%s (ID %d)", parsed.Author.String(), parsed.Author.ID), IconURL: discordgo.EndpointUserAvatar(parsed.Author.ID, parsed.Author.Avatar), }, Description: fmt.Sprintf("🔍**Reported** %s *(ID %d)*\n📄**Reason:** %s ([Logs](%s))\n**Channel:** <#%d>", target.String(), target.ID, parsed.Args[1].Value, logLink, parsed.ChannelID), Color: 0xee82ee, Thumbnail: &discordgo.MessageEmbedThumbnail{ URL: discordgo.EndpointUserAvatar(target.ID, target.Avatar), }, } send := &discordgo.MessageSend{ Content: topContent, Embeds: []*discordgo.MessageEmbed{embed}, AllowedMentions: discordgo.AllowedMentions{ Parse: []discordgo.AllowedMentionType{discordgo.AllowedMentionTypeUsers}, }, } _, err = common.BotSession.ChannelMessageSendComplex(channelID, send) if err != nil { return "Something went wrong while sending your report!", err } if channelID != parsed.ChannelID || parsed.SlashCommandTriggerData != nil { return "User reported to the proper authorities!", nil } return nil, nil }, }, { CustomEnabled: true, CmdCategory: commands.CategoryModeration, Name: "Clean", Description: "Delete the last number of messages from chat, optionally filtering by user, max age and regex or ignoring pinned messages.", LongDescription: "Specify a regex with \"-r regex_here\" and max age with \"-ma 1h10m\"\nYou can invert the regex match (i.e. only clear messages that do not match the given regex) by supplying the `-im` flag\nNote: Will only look in the last 1k messages, and none > 2 weeks old.", Aliases: []string{"clear", "cl"}, RequiredArgs: 1, Arguments: []*dcmd.ArgDef{ {Name: "Num", Type: &dcmd.IntArg{Min: 1, Max: 100}}, {Name: "User", Type: dcmd.UserID, Default: 0}, }, ArgSwitches: []*dcmd.ArgDef{ {Name: "r", Help: "Regex", Type: dcmd.String}, {Name: "im", Help: "Invert regex match"}, {Name: "ma", Help: "Max age", Default: time.Duration(0), Type: &commands.DurationArg{}}, {Name: "minage", Help: "Min age", Default: time.Duration(0), Type: &commands.DurationArg{}}, {Name: "i", Help: "Regex case insensitive"}, {Name: "nopin", Help: "Ignore pinned messages"}, {Name: "a", Help: "Only remove messages with attachments"}, {Name: "to", Help: "Stop at this msg ID", Type: dcmd.BigInt}, {Name: "from", Help: "Start at this msg ID", Type: dcmd.BigInt}, }, RequiredDiscordPermsHelp: "ManageMessages or ManageGuild", RequireBotPerms: [][]int64{{discordgo.PermissionAdministrator}, {discordgo.PermissionManageMessages}}, ArgumentCombos: [][]int{{0}, {0, 1}, {1, 0}}, SlashCommandEnabled: true, DefaultEnabled: false, IsResponseEphemeral: false, RunFunc: func(parsed *dcmd.Data) (interface{}, error) { config, _, err := MBaseCmd(parsed, 0) if err != nil { return nil, err } _, err = MBaseCmdSecond(parsed, "", true, discordgo.PermissionManageMessages, nil, config.CleanEnabled, false) if err != nil { return nil, err } var filters []MessageFilter if userIDFilter := parsed.Args[1].Int64(); userIDFilter != 0 { filters = append(filters, &MessageAuthorFilter{userIDFilter}) } if re := parsed.Switches["r"].Str(); re != "" { if caseInsensitive := parsed.Switches["i"].Bool(); caseInsensitive { if !strings.HasPrefix(re, "(?i)") { re = "(?i)" + re } } parsedRe, err := regexp.Compile(re) if err != nil { return "Invalid regexp", err } invertMatch := parsed.Switches["im"].Bool() filters = append(filters, &RegExpFilter{InvertMatch: invertMatch, Re: parsedRe}) } now := time.Now() minAge := parsed.Switches["minage"].Value.(time.Duration) maxAge := parsed.Switches["ma"].Value.(time.Duration) if minAge != 0 || maxAge != 0 { filters = append(filters, &MessageAgeFilter{ReferenceTime: now, MinAge: minAge, MaxAge: maxAge}) } fromID := parsed.Switches["from"].Int64() toID := parsed.Switches["to"].Int64() if fromID != 0 || toID != 0 { filters = append(filters, &MessageIDFilter{FromID: fromID, ToID: toID}) } if parsed.Switches["nopin"].Bool() { pinned, err := common.BotSession.ChannelMessagesPinned(parsed.ChannelID) if err != nil { return "Failed fetching pinned messages", err } filters = append(filters, NewIgnorePinnedMessagesFilter(pinned)) } if onlyDeleteWithAttachments := parsed.Switches["a"].Bool(); onlyDeleteWithAttachments { filters = append(filters, &MessagesWithAttachmentsFilter{}) } var triggerID int64 if parsed.TriggerType == dcmd.TriggerTypeSlashCommands { m, err := common.BotSession.GetOriginalInteractionResponse(common.BotApplication.ID, parsed.SlashCommandTriggerData.Interaction.Token) if err != nil { return "Failed fetching original interaction response", err } triggerID = m.ID } else { triggerID = parsed.TraditionalTriggerData.Message.ID } deleteLimit := parsed.Args[0].Int() fetchLimit := deleteLimit + 1 if len(filters) > 0 { fetchLimit = deleteLimit * 50 } if fetchLimit > 1000 { fetchLimit = 1000 } msgs, err := bot.GetMessages(parsed.GuildData.GS.ID, parsed.ChannelID, fetchLimit, false) if err != nil { return "Failed fetching messages", err } var toDelete []int64 filter := CombinedANDFilter{filters} for _, msg := range msgs { if now.Sub(msg.ParsedCreatedAt) > (14*time.Hour*24)-time.Minute { continue } if msg.ID == triggerID { continue } if filter.Matches(msg) { toDelete = append(toDelete, msg.ID) if len(toDelete) >= deleteLimit { break } } } var resp string switch numDeleted := len(toDelete); numDeleted { case 0: resp = "Deleted 0 messages! :')" case 1: err = common.BotSession.ChannelMessageDelete(parsed.ChannelID, toDelete[0]) resp = "Deleted 1 message! :')" default: err = common.BotSession.ChannelMessagesBulkDelete(parsed.ChannelID, toDelete) resp = fmt.Sprintf("Deleted %d messages! :')", numDeleted) } if err != nil { return "Failed deleting messages", err } return dcmd.NewTemporaryResponse(time.Second*5, resp, true), nil }, }, { CustomEnabled: true, CmdCategory: commands.CategoryModeration, Name: "Reason", Description: "Add/Edit a modlog reason", RequiredArgs: 2, Arguments: []*dcmd.ArgDef{ {Name: "Message-ID", Type: dcmd.BigInt}, {Name: "Reason", Type: dcmd.String}, }, RequiredDiscordPermsHelp: "KickMembers or ManageGuild", SlashCommandEnabled: true, DefaultEnabled: false, RunFunc: func(parsed *dcmd.Data) (interface{}, error) { config, _, err := MBaseCmd(parsed, 0) if err != nil { return nil, err } _, err = MBaseCmdSecond(parsed, "", true, discordgo.PermissionKickMembers, nil, true, false) if err != nil { return nil, err } if config.ActionChannel == 0 { return "No mod log channel set up", nil } msg, err := common.BotSession.ChannelMessage(config.ActionChannel, parsed.Args[0].Int64()) if err != nil { return nil, err } if msg.Author.ID != common.BotUser.ID { return "I didn't make that message", nil } if len(msg.Embeds) < 1 { return "This entry is either too old or you're trying to mess with me...", nil } embed := msg.Embeds[0] updateEmbedReason(parsed.Author, parsed.Args[1].Str(), embed) _, err = common.BotSession.ChannelMessageEditEmbed(config.ActionChannel, msg.ID, embed) if err != nil { return nil, err } return "👌", nil }, }, { CustomEnabled: true, CmdCategory: commands.CategoryModeration, Name: "Warn", Description: "Warns a user, warnings are saved using the bot. Use -warnings to view them.", RequiredArgs: 2, Arguments: []*dcmd.ArgDef{ {Name: "User", Type: dcmd.UserID}, {Name: "Reason", Type: dcmd.String}, }, RequiredDiscordPermsHelp: "ManageMessages or ManageGuild", SlashCommandEnabled: true, DefaultEnabled: false, IsResponseEphemeral: false, RunFunc: func(parsed *dcmd.Data) (interface{}, error) { if parsed.Context().Value(commands.CtxKeyExecutedByNestedCommandTemplate) == true { return nil, errors.New("cannot nest exec/execAdmin calls") } config, target, err := MBaseCmd(parsed, parsed.Args[0].Int64()) if err != nil { return nil, err } _, err = MBaseCmdSecond(parsed, "", true, discordgo.PermissionManageMessages, config.WarnCmdRoles, config.WarnCommandsEnabled, true) if err != nil { return nil, err } member, err := bot.GetMember(parsed.GuildData.GS.ID, target.ID) if err != nil || member == nil { return "Member not found", err } var msg *discordgo.Message if parsed.TraditionalTriggerData != nil { msg = parsed.TraditionalTriggerData.Message } err = WarnUser(config, parsed.GuildData.GS.ID, parsed.GuildData.CS, msg, parsed.Author, target, parsed.Args[1].Str(), parsed.Context().Value(commands.CtxKeyExecutedByCommandTemplate) == true) if err != nil { return nil, err } return GenericCmdResp(MAWarned, target, 0, false, true), nil }, }, { CustomEnabled: true, CmdCategory: commands.CategoryModeration, Name: "Warnings", Description: "Lists warning of a user.", Aliases: []string{"Warns"}, RequiredArgs: 1, Arguments: []*dcmd.ArgDef{ {Name: "User", Type: dcmd.UserID, Default: 0}, {Name: "Page", Type: &dcmd.IntArg{Max: 10000}, Default: 0}, }, ArgSwitches: []*dcmd.ArgDef{ {Name: "id", Help: "Warning ID", Type: dcmd.Int}, }, RequiredDiscordPermsHelp: "ManageMessages or ManageGuild", SlashCommandEnabled: true, DefaultEnabled: false, RunFunc: func(parsed *dcmd.Data) (interface{}, error) { var err error config, _, err := MBaseCmd(parsed, 0) if err != nil { return nil, err } _, err = MBaseCmdSecond(parsed, "", true, discordgo.PermissionManageMessages, config.WarnCmdRoles, true, true) if err != nil { return nil, err } if parsed.Switches["id"].Value != nil { id := parsed.Switches["id"].Int() warning, err := models.ModerationWarnings( models.ModerationWarningWhere.ID.EQ(id), models.ModerationWarningWhere.GuildID.EQ(parsed.GuildData.GS.ID), ).OneG(parsed.Context()) if err != nil { if err == sql.ErrNoRows { return fmt.Sprintf("Could not find warning with ID `%d`", id), nil } return nil, err } return &discordgo.MessageEmbed{ Title: fmt.Sprintf("Warning#%d - User : %s", warning.ID, warning.UserID), Description: fmt.Sprintf("<t:%d:f> - **Reason** : %s", warning.CreatedAt.Unix(), warning.Message), Footer: &discordgo.MessageEmbedFooter{Text: fmt.Sprintf("By: %s (%13s)", warning.AuthorUsernameDiscrim, warning.AuthorID)}, }, nil } page := parsed.Args[1].Int() if page < 1 { page = 1 } if parsed.Context().Value(paginatedmessages.CtxKeyNoPagination) != nil { return PaginateWarnings(parsed)(nil, page) } return paginatedmessages.NewPaginatedResponse(parsed.GuildData.GS.ID, parsed.GuildData.CS.ID, page, 0, PaginateWarnings(parsed)), nil }, }, { CustomEnabled: true, CmdCategory: commands.CategoryModeration, Name: "EditWarning", Description: "Edit a warning, id is the first number of each warning from the warnings command", RequiredArgs: 2, Arguments: []*dcmd.ArgDef{ {Name: "Id", Type: dcmd.Int}, {Name: "NewMessage", Type: dcmd.String}, }, RequiredDiscordPermsHelp: "ManageMessages or ManageGuild", SlashCommandEnabled: true, DefaultEnabled: false, RunFunc: func(parsed *dcmd.Data) (interface{}, error) { config, _, err := MBaseCmd(parsed, 0) if err != nil { return nil, err } _, err = MBaseCmdSecond(parsed, "", true, discordgo.PermissionManageMessages, config.WarnCmdRoles, config.WarnCommandsEnabled, true) if err != nil { return nil, err } warningID := parsed.Args[0].Int() updatedMessage := fmt.Sprintf("%s (updated by %s (%d))", parsed.Args[1].Str(), parsed.Author.String(), parsed.Author.ID) numUpdated, err := models.ModerationWarnings( models.ModerationWarningWhere.ID.EQ(warningID), models.ModerationWarningWhere.GuildID.EQ(parsed.GuildData.GS.ID), ).UpdateAllG(parsed.Context(), models.M{"message": updatedMessage}) if err != nil { return "Failed editing warning", err } if numUpdated == 0 { return fmt.Sprintf("Could not find warning with ID `%d`", warningID), nil } return "👌", nil }, }, { CustomEnabled: true, CmdCategory: commands.CategoryModeration, Name: "DelWarning", Aliases: []string{"dw", "delwarn", "deletewarning"}, Description: "Deletes a warning, id is the first number of each warning from the warnings command", RequiredArgs: 1, Arguments: []*dcmd.ArgDef{ {Name: "Id", Type: dcmd.Int}, {Name: "Reason", Type: dcmd.String}, }, RequiredDiscordPermsHelp: "ManageMessages or ManageGuild", SlashCommandEnabled: true, DefaultEnabled: false, RunFunc: func(parsed *dcmd.Data) (interface{}, error) { config, _, err := MBaseCmd(parsed, 0) if err != nil { return nil, err } _, err = MBaseCmdSecond(parsed, "", true, discordgo.PermissionManageMessages, config.WarnCmdRoles, config.WarnCommandsEnabled, true) if err != nil { return nil, err } warningID := parsed.Args[0].Int() numDeleted, err := models.ModerationWarnings( models.ModerationWarningWhere.ID.EQ(warningID), models.ModerationWarningWhere.GuildID.EQ(parsed.GuildData.GS.ID), ).DeleteAllG(parsed.Context()) if err != nil { return "Failed deleting warning", err } if numDeleted == 0 { return fmt.Sprintf("Could not find warning with ID `%d`", warningID), nil } return "👌", nil }, }, { CustomEnabled: true, CmdCategory: commands.CategoryModeration, Name: "ClearWarnings", Aliases: []string{"clw"}, Description: "Clears the warnings of a user", RequiredArgs: 1, Arguments: []*dcmd.ArgDef{ {Name: "User", Type: dcmd.UserID}, {Name: "Reason", Type: dcmd.String}, }, RequiredDiscordPermsHelp: "ManageMessages or ManageGuild", SlashCommandEnabled: true, DefaultEnabled: false, RunFunc: func(parsed *dcmd.Data) (interface{}, error) { config, target, err := MBaseCmd(parsed, parsed.Args[0].Int64()) if err != nil { return nil, err } _, err = MBaseCmdSecond(parsed, "", true, discordgo.PermissionManageMessages, config.WarnCmdRoles, config.WarnCommandsEnabled, true) if err != nil { return nil, err } numDeleted, err := models.ModerationWarnings( models.ModerationWarningWhere.GuildID.EQ(parsed.GuildData.GS.ID), models.ModerationWarningWhere.UserID.EQ(discordgo.StrID(target.ID)), ).DeleteAllG(parsed.Context()) if err != nil { return "Failed deleting warnings", err } reason := parsed.Args[1].Str() err = CreateModlogEmbed(config, parsed.Author, MAClearWarnings, target, reason, "") if err != nil { return "Failed sending modlog", err } return fmt.Sprintf("Deleted %d warnings.", numDeleted), nil }, }, { CmdCategory: commands.CategoryModeration, Name: "TopWarnings", Aliases: []string{"topwarns"}, Description: "Shows ranked list of warnings on the server", Arguments: []*dcmd.ArgDef{ {Name: "Page", Type: dcmd.Int, Default: 0}, }, ArgSwitches: []*dcmd.ArgDef{ {Name: "id", Help: "List userIDs"}, }, RequiredDiscordPermsHelp: "ManageMessages or ManageGuild", SlashCommandEnabled: true, DefaultEnabled: false, RunFunc: paginatedmessages.PaginatedCommand(0, func(parsed *dcmd.Data, p *paginatedmessages.PaginatedMessage, page int) (*discordgo.MessageEmbed, error) { showUserIDs := false config, _, err := MBaseCmd(parsed, 0) if err != nil { return nil, err } _, err = MBaseCmdSecond(parsed, "", true, discordgo.PermissionManageMessages, config.WarnCmdRoles, true, true) if err != nil { return nil, err } if parsed.Switches["id"].Value != nil && parsed.Switches["id"].Value.(bool) { showUserIDs = true } offset := (page - 1) * 15 entries, err := TopWarns(parsed.GuildData.GS.ID, offset, 15) if err != nil { return nil, err } if len(entries) < 1 && p != nil && p.LastResponse != nil { return nil, paginatedmessages.ErrNoResults } embed := &discordgo.MessageEmbed{ Title: "Ranked list of warnings", } out := "```\n# - Warns - User\n" for _, v := range entries { if !showUserIDs { user := v.Username if user == "" { user = "unknown ID:" + strconv.FormatInt(v.UserID, 10) } out += fmt.Sprintf("#%02d: %4d - %s\n", v.Rank, v.WarnCount, user) } else { out += fmt.Sprintf("#%02d: %4d - %d\n", v.Rank, v.WarnCount, v.UserID) } } count, err := models.ModerationWarnings(models.ModerationWarningWhere.GuildID.EQ(parsed.GuildData.GS.ID)).CountG(context.Background()) if err != nil { return nil, err } out += "```\n" + fmt.Sprintf("Total Server Warnings: `%d`", count) embed.Description = out return embed, nil }), }, { CustomEnabled: true, CmdCategory: commands.CategoryModeration, Name: "GiveRole", Aliases: []string{"grole", "arole", "addrole"}, Description: "Gives a role to the specified member, with optional expiry", RequiredArgs: 2, Arguments: []*dcmd.ArgDef{ {Name: "User", Type: dcmd.UserID}, {Name: "Role", Type: &commands.RoleArg{}}, {Name: "Duration", Type: &commands.DurationArg{}, Default: time.Duration(0)}, }, RequiredDiscordPermsHelp: "ManageRoles or ManageGuild", RequireBotPerms: [][]int64{{discordgo.PermissionAdministrator}, {discordgo.PermissionManageGuild}, {discordgo.PermissionManageRoles}}, SlashCommandEnabled: true, DefaultEnabled: false, RunFunc: func(parsed *dcmd.Data) (interface{}, error) { config, target, err := MBaseCmd(parsed, parsed.Args[0].Int64()) if err != nil { return nil, err } _, err = MBaseCmdSecond(parsed, "", true, discordgo.PermissionManageRoles, config.GiveRoleCmdRoles, config.GiveRoleCmdEnabled, true) if err != nil { return nil, err } member, err := bot.GetMember(parsed.GuildData.GS.ID, target.ID) if err != nil || member == nil { return "Member not found", err } role := parsed.Args[1].Value.(*discordgo.Role) if role == nil { return "Couldn't find the specified role", nil } if !bot.IsMemberAboveRole(parsed.GuildData.GS, parsed.GuildData.MS, role) { return "Can't give roles above you", nil } dur := parsed.Args[2].Value.(time.Duration) if common.ContainsInt64Slice(member.Member.Roles, role.ID) && dur <= 0 { return "That user already has that role", nil } err = common.AddRoleDS(member, role.ID) if err != nil { return nil, err } if dur > 0 { err := scheduledevents2.ScheduleRemoveRole(parsed.Context(), parsed.GuildData.GS.ID, target.ID, role.ID, time.Now().Add(dur)) if err != nil { return nil, err } } scheduledevents2.CancelAddRole(parsed.Context(), parsed.GuildData.GS.ID, parsed.Author.ID, role.ID) action := MAGiveRole action.Prefix = "Gave the role " + role.Name + " to " if config.GiveRoleCmdModlog && config.ActionChannel != 0 { if dur > 0 { action.Footer = "Duration: " + common.HumanizeDuration(common.DurationPrecisionMinutes, dur) } CreateModlogEmbed(config, parsed.Author, action, target, "", "") } return GenericCmdResp(action, target, dur, true, dur <= 0), nil }, }, { CustomEnabled: true, CmdCategory: commands.CategoryModeration, Name: "RemoveRole", Aliases: []string{"rrole", "takerole", "trole"}, Description: "Removes the specified role from the target", RequiredArgs: 2, Arguments: []*dcmd.ArgDef{ {Name: "User", Type: dcmd.UserID}, {Name: "Role", Type: &commands.RoleArg{}}, }, RequiredDiscordPermsHelp: "ManageRoles or ManageGuild", RequireBotPerms: [][]int64{{discordgo.PermissionAdministrator}, {discordgo.PermissionManageGuild}, {discordgo.PermissionManageRoles}}, SlashCommandEnabled: true, DefaultEnabled: false, RunFunc: func(parsed *dcmd.Data) (interface{}, error) { config, target, err := MBaseCmd(parsed, parsed.Args[0].Int64()) if err != nil { return nil, err } _, err = MBaseCmdSecond(parsed, "", true, discordgo.PermissionManageRoles, config.GiveRoleCmdRoles, config.GiveRoleCmdEnabled, true) if err != nil { return nil, err } member, err := bot.GetMember(parsed.GuildData.GS.ID, target.ID) if err != nil || member == nil { return "Member not found", err } role := parsed.Args[1].Value.(*discordgo.Role) if role == nil { return "Couldn't find the specified role", nil } if !bot.IsMemberAboveRole(parsed.GuildData.GS, parsed.GuildData.MS, role) { return "Can't remove roles above you", nil } err = common.RemoveRoleDS(member, role.ID) if err != nil { return nil, err } scheduledevents2.CancelRemoveRole(parsed.Context(), parsed.GuildData.GS.ID, parsed.Author.ID, role.ID) action := MARemoveRole action.Prefix = "Removed the role " + role.Name + " from " if config.GiveRoleCmdModlog && config.ActionChannel != 0 { CreateModlogEmbed(config, parsed.Author, action, target, "", "") } return GenericCmdResp(action, target, 0, true, true), nil }, }, }
var PageHTML string
Functions ¶
func AddMemberMuteRole ¶
func BanUserWithDuration ¶
func CreateModlogEmbed ¶
func DeleteMessages ¶
func FindAuditLogEntry ¶
func FindAuditLogEntry(guildID int64, typ discordgo.AuditLogAction, targetUser int64, within time.Duration) (author *discordgo.User, entry *discordgo.AuditLogEntry)
func GenericCmdResp ¶
func HandleChannelCreateUpdate ¶
func HandleChannelCreateUpdate(evt *eventsystem.EventData) (retry bool, err error)
func HandleClearServerWarnings ¶
func HandleClearServerWarnings(w http.ResponseWriter, r *http.Request) (web.TemplateData, error)
Clear all server warnigns
func HandleGuildBanAddRemove ¶
func HandleGuildBanAddRemove(evt *eventsystem.EventData)
func HandleGuildCreate ¶
func HandleGuildCreate(evt *eventsystem.EventData)
func HandleGuildMemberRemove ¶
func HandleGuildMemberRemove(evt *eventsystem.EventData) (retry bool, err error)
func HandleGuildMemberTimeoutChange ¶ added in v2.4.0
func HandleGuildMemberTimeoutChange(evt *eventsystem.EventData) (retry bool, err error)
func HandleGuildMemberUpdate ¶
func HandleGuildMemberUpdate(evt *eventsystem.EventData) (retry bool, err error)
func HandleMemberJoin ¶
func HandleMemberJoin(evt *eventsystem.EventData) (retry bool, err error)
func HandleModeration ¶
func HandleModeration(w http.ResponseWriter, r *http.Request) (web.TemplateData, error)
HandleModeration servers the moderation page itself
func HandlePostModeration ¶
func HandlePostModeration(w http.ResponseWriter, r *http.Request) (web.TemplateData, error)
HandlePostModeration updates the settings
func LockMemberMuteMW ¶
func LockMemberMuteMW(next eventsystem.HandlerFunc) eventsystem.HandlerFunc
Since updating mutes are now a complex operation with removing roles and whatnot, to avoid weird bugs from happening we lock it so it can only be updated one place per user
func MBaseCmdSecond ¶
func MuteUnmuteUser ¶
func MuteUnmuteUser(config *Config, mute bool, guildID int64, channel *dstate.ChannelState, message *discordgo.Message, author *discordgo.User, reason string, member *dstate.MemberState, duration int, executedByCommandTemplate bool) error
Unmut or mute a user, ignore duration if unmuting TODO: i don't think we need to track mutes in its own database anymore now with the new scheduled event system
func PaginateWarnings ¶
func PaginateWarnings(parsed *dcmd.Data) func(p *paginatedmessages.PaginatedMessage, page int) (*discordgo.MessageEmbed, error)
func RedisKeyBannedUser ¶
func RedisKeyLockedMute ¶
func RedisKeyMutedUser ¶
func RedisKeyUnbannedUser ¶
func RefreshMuteOverrideForChannel ¶
func RefreshMuteOverrideForChannel(config *Config, channel dstate.ChannelState)
func RefreshMuteOverrides ¶
Refreshes the mute override on the channel, currently it only adds it.
func RegisterPlugin ¶
func RegisterPlugin()
func RemoveMemberMuteRole ¶
func RemoveTimeout ¶ added in v2.3.0
func SaveConfig ¶ added in v2.40.0
func TimeoutUser ¶ added in v2.3.0
func UnlockMute ¶
func UnlockMute(uID int64)
Types ¶
type CombinedANDFilter ¶ added in v2.40.0
type CombinedANDFilter struct{ Filters []MessageFilter }
All the child filters need to match for the message to be deleted.
func (*CombinedANDFilter) Matches ¶ added in v2.40.0
func (f *CombinedANDFilter) Matches(msg *dstate.MessageState) (delete bool)
type Config ¶
type Config struct { GuildID int64 CreatedAt time.Time UpdatedAt time.Time // Kick KickEnabled bool KickCmdRoles types.Int64Array `valid:"role,true"` DeleteMessagesOnKick bool KickReasonOptional bool KickMessage string `valid:"template,5000"` // Ban BanEnabled bool BanCmdRoles types.Int64Array `valid:"role,true"` BanReasonOptional bool BanMessage string `valid:"template,5000"` DefaultBanDeleteDays null.Int64 `valid:"0,7"` // Timeout TimeoutEnabled bool TimeoutCmdRoles types.Int64Array `valid:"role,true"` TimeoutReasonOptional bool TimeoutRemoveReasonOptional bool TimeoutMessage string `valid:"template,5000"` DefaultTimeoutDuration null.Int64 `valid:"1,40320"` // Mute/unmute MuteEnabled bool MuteCmdRoles types.Int64Array `valid:"role,true"` MuteRole int64 `valid:"role,true"` MuteDisallowReactionAdd bool MuteReasonOptional bool UnmuteReasonOptional bool MuteManageRole bool MuteRemoveRoles types.Int64Array `valid:"role,true"` MuteIgnoreChannels types.Int64Array `valid:"channel,true"` MuteMessage string `valid:"template,5000"` UnmuteMessage string `valid:"template,5000"` DefaultMuteDuration null.Int64 `valid:"0,"` // Warn WarnCommandsEnabled bool WarnCmdRoles types.Int64Array `valid:"role,true"` WarnIncludeChannelLogs bool WarnSendToModlog bool WarnMessage string `valid:"template,5000"` // Misc CleanEnabled bool ReportEnabled bool ActionChannel int64 `valid:"channel,true"` ReportChannel int64 `valid:"channel,true"` ErrorChannel int64 `valid:"channel,true"` LogUnbans bool LogBans bool LogKicks bool LogTimeouts bool GiveRoleCmdEnabled bool GiveRoleCmdModlog bool GiveRoleCmdRoles types.Int64Array `valid:"role,true"` }
func BotCachedGetConfig ¶ added in v2.41.0
func BotCachedGetConfigIfNotSet ¶ added in v2.41.0
func FetchConfig ¶ added in v2.41.0
func (*Config) ToModel ¶ added in v2.40.0
func (c *Config) ToModel() *models.ModerationConfig
type IgnorePinnedMessagesFilter ¶ added in v2.40.0
type IgnorePinnedMessagesFilter struct {
PinnedMsgIDs map[int64]struct{}
}
Do not delete pinned messages.
func NewIgnorePinnedMessagesFilter ¶ added in v2.40.0
func NewIgnorePinnedMessagesFilter(pinned []*discordgo.Message) *IgnorePinnedMessagesFilter
func (*IgnorePinnedMessagesFilter) Matches ¶ added in v2.40.0
func (f *IgnorePinnedMessagesFilter) Matches(msg *dstate.MessageState) (delete bool)
type MessageAgeFilter ¶ added in v2.40.0
type MessageAgeFilter struct { ReferenceTime time.Time // Calculate the age of messages relative to this time. // 0 means no min age requirement (and likewise for max age.) MinAge time.Duration MaxAge time.Duration }
Only delete messages satisfying MinAge<=age<=MaxAge.
func (*MessageAgeFilter) Matches ¶ added in v2.40.0
func (f *MessageAgeFilter) Matches(msg *dstate.MessageState) (delete bool)
type MessageAuthorFilter ¶ added in v2.40.0
type MessageAuthorFilter struct{ UserID int64 }
Only delete messages from the specified user.
func (*MessageAuthorFilter) Matches ¶ added in v2.40.0
func (f *MessageAuthorFilter) Matches(msg *dstate.MessageState) (delete bool)
type MessageFilter ¶ added in v2.40.0
type MessageFilter interface {
Matches(msg *dstate.MessageState) (delete bool)
}
type MessageIDFilter ¶ added in v2.40.0
type MessageIDFilter struct { // 0 means no start ID set (and likewise for end ID.) FromID int64 ToID int64 }
Only delete messages satisfying ToID<=id<=FromID.
func (*MessageIDFilter) Matches ¶ added in v2.40.0
func (f *MessageIDFilter) Matches(msg *dstate.MessageState) (delete bool)
type MessagesWithAttachmentsFilter ¶ added in v2.40.0
type MessagesWithAttachmentsFilter struct{}
Only delete messages with attachments.
func (*MessagesWithAttachmentsFilter) Matches ¶ added in v2.40.0
func (*MessagesWithAttachmentsFilter) Matches(msg *dstate.MessageState) (delete bool)
type ModlogAction ¶
func (ModlogAction) String ¶
func (m ModlogAction) String() string
type Plugin ¶
type Plugin struct{}
func (*Plugin) AddCommands ¶
func (p *Plugin) AddCommands()
func (*Plugin) AllFeatureFlags ¶
func (*Plugin) LoadServerHomeWidget ¶
func (p *Plugin) LoadServerHomeWidget(w http.ResponseWriter, r *http.Request) (web.TemplateData, error)
func (*Plugin) PluginInfo ¶
func (p *Plugin) PluginInfo() *common.PluginInfo
func (*Plugin) ShardMigrationReceive ¶
func (p *Plugin) ShardMigrationReceive(evt dshardorchestrator.EventType, data interface{})
type Punishment ¶
type Punishment int
const ( PunishmentKick Punishment = iota PunishmentBan PunishmentTimeout )
type RegExpFilter ¶ added in v2.40.0
Only delete messages matching the regex (or, if InvertMatch==true, only delete messages not matching the regex.)
func (*RegExpFilter) Matches ¶ added in v2.40.0
func (f *RegExpFilter) Matches(msg *dstate.MessageState) (delete bool)
type ScheduledUnbanData ¶
type ScheduledUnbanData struct {
UserID int64 `json:"user_id"`
}
type ScheduledUnmuteData ¶
type ScheduledUnmuteData struct {
UserID int64 `json:"user_id"`
}
type TemplatesWarning ¶ added in v2.40.0
type TemplatesWarning struct { ID int CreatedAt time.Time UpdatedAt time.Time GuildID int64 UserID int64 AuthorID string AuthorUsernameDiscrim string Message string LogsLink string }
Needed to maintain backward compatibility with previous implementation of getWarnings using gorm, in which LogsLink was marked as a string instead of a null.String.