feat: add moderation commands and update README
- Introduced a new set of moderation commands including `/moderation purge`, `/moderation timeout`, `/moderation kick`, `/moderation ban`, `/moderation unban`, `/moderation untimeout`, and `/moderation slowmode`. - Updated README to include details about the new moderation features and commands. - Enhanced help command to display moderation tools and their usage.
This commit is contained in:
@@ -10,8 +10,9 @@ It currently includes:
|
|||||||
- Scheduling (1:1 sessions with DM invites + reaction-based accept/decline/reschedule)
|
- Scheduling (1:1 sessions with DM invites + reaction-based accept/decline/reschedule)
|
||||||
- Projects (list/create projects + helpers)
|
- Projects (list/create projects + helpers)
|
||||||
- Music (Lavalink-powered `/play`, `/queue`, `/volume`)
|
- Music (Lavalink-powered `/play`, `/queue`, `/volume`)
|
||||||
- Twitch live notifications (per-guild `/config` setup)
|
- Twitch live notifications (per-guild `/config` setup)
|
||||||
- Welcome messages, DMs, and default role assignment (per-guild `/config` setup)
|
- Welcome messages, DMs, and default role assignment (per-guild `/config` setup)
|
||||||
|
- Focused moderation tools (`/moderation purge|timeout|untimeout|kick|ban|unban|slowmode`)
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
@@ -96,6 +97,14 @@ just down # stop postgres
|
|||||||
- `/projects list`, `/projects create`, `/projects add-helper`
|
- `/projects list`, `/projects create`, `/projects add-helper`
|
||||||
- **Music** (requires **DJ** role)
|
- **Music** (requires **DJ** role)
|
||||||
- `/play`, `/queue`, `/volume`
|
- `/play`, `/queue`, `/volume`
|
||||||
|
- **Moderation** (focused + practical)
|
||||||
|
- `/moderation purge amount:<1-100>` — Fast recent message cleanup (**Manage Messages**)
|
||||||
|
- `/moderation timeout user:@someone duration:<10m|1h|...> [reason]` — Temporary member timeout (**Moderate Members**)
|
||||||
|
- `/moderation kick user:@someone [reason]` — Kick member (**Kick Members**)
|
||||||
|
- `/moderation ban user:@someone [delete_days] [reason]` — Ban member (**Ban Members**)
|
||||||
|
- `/moderation unban user_id:<id> [reason]` — Unban by user ID (**Ban Members**)
|
||||||
|
- `/moderation untimeout user:@someone [reason]` — Remove timeout (**Moderate Members**)
|
||||||
|
- `/moderation slowmode channel:#channel seconds:<0-21600>` — Control channel flood (**Manage Channels**)
|
||||||
- **Twitch live notifications** (**Manage Server**)
|
- **Twitch live notifications** (**Manage Server**)
|
||||||
- `/config addstreamer username:<twitch-name>` — Add a streamer for notifications
|
- `/config addstreamer username:<twitch-name>` — Add a streamer for notifications
|
||||||
- `/config removestreamer username:<twitch-name>` — Remove a streamer
|
- `/config removestreamer username:<twitch-name>` — Remove a streamer
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"velox-bot/internal/commands/help"
|
"velox-bot/internal/commands/help"
|
||||||
cmdlevel "velox-bot/internal/commands/level"
|
cmdlevel "velox-bot/internal/commands/level"
|
||||||
"velox-bot/internal/commands/meeting"
|
"velox-bot/internal/commands/meeting"
|
||||||
|
"velox-bot/internal/commands/moderation"
|
||||||
cmdmusic "velox-bot/internal/commands/music"
|
cmdmusic "velox-bot/internal/commands/music"
|
||||||
"velox-bot/internal/commands/projects"
|
"velox-bot/internal/commands/projects"
|
||||||
"velox-bot/internal/commands/schedule"
|
"velox-bot/internal/commands/schedule"
|
||||||
@@ -32,6 +33,7 @@ var AllCommands = []*discordgo.ApplicationCommand{
|
|||||||
cmdmusic.Queue,
|
cmdmusic.Queue,
|
||||||
cmdmusic.Volume,
|
cmdmusic.Volume,
|
||||||
meeting.Meeting,
|
meeting.Meeting,
|
||||||
|
moderation.Moderation,
|
||||||
timezone.Timezone,
|
timezone.Timezone,
|
||||||
schedule.Schedule,
|
schedule.Schedule,
|
||||||
projects.Projects,
|
projects.Projects,
|
||||||
@@ -52,6 +54,7 @@ var handlers = map[string]func(s *discordgo.Session, i *discordgo.InteractionCre
|
|||||||
"leaderboard": cmdlevel.LeaderboardHandler,
|
"leaderboard": cmdlevel.LeaderboardHandler,
|
||||||
"rewards": cmdlevel.RewardsHandler,
|
"rewards": cmdlevel.RewardsHandler,
|
||||||
"meeting": meeting.MeetingHandler,
|
"meeting": meeting.MeetingHandler,
|
||||||
|
"moderation": moderation.ModerationHandler,
|
||||||
"timezone": timezone.TimezoneHandler,
|
"timezone": timezone.TimezoneHandler,
|
||||||
"schedule": schedule.ScheduleHandler,
|
"schedule": schedule.ScheduleHandler,
|
||||||
"projects": projects.ProjectsHandler,
|
"projects": projects.ProjectsHandler,
|
||||||
|
|||||||
@@ -94,6 +94,17 @@ func HelpHandler(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
|||||||
"`/meeting invite user:@someone` — Allow a user to join\n" +
|
"`/meeting invite user:@someone` — Allow a user to join\n" +
|
||||||
"`/meeting uninvite user:@someone` — Remove a user's permission",
|
"`/meeting uninvite user:@someone` — Remove a user's permission",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "Moderation (focused tools)",
|
||||||
|
Value: "" +
|
||||||
|
"`/moderation purge amount:<1-100>` — Delete recent messages (**Manage Messages**)\n" +
|
||||||
|
"`/moderation timeout user:@someone duration:<10m|1h|...> [reason]` — Timeout member (**Moderate Members**)\n" +
|
||||||
|
"`/moderation kick user:@someone [reason]` — Kick member (**Kick Members**)\n" +
|
||||||
|
"`/moderation ban user:@someone [delete_days] [reason]` — Ban member (**Ban Members**)\n" +
|
||||||
|
"`/moderation unban user_id:<id> [reason]` — Unban by user ID (**Ban Members**)\n" +
|
||||||
|
"`/moderation untimeout user:@someone [reason]` — Remove timeout (**Moderate Members**)\n" +
|
||||||
|
"`/moderation slowmode channel:#channel seconds:<0-21600>` — Set slowmode (**Manage Channels**)",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Name: "Leveling",
|
Name: "Leveling",
|
||||||
Value: "" +
|
Value: "" +
|
||||||
|
|||||||
@@ -0,0 +1,540 @@
|
|||||||
|
package public
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/bwmarrin/discordgo"
|
||||||
|
)
|
||||||
|
|
||||||
|
var Moderation = &discordgo.ApplicationCommand{
|
||||||
|
Name: "moderation",
|
||||||
|
Description: "High-value moderation tools",
|
||||||
|
Options: []*discordgo.ApplicationCommandOption{
|
||||||
|
{
|
||||||
|
Type: discordgo.ApplicationCommandOptionSubCommand,
|
||||||
|
Name: "purge",
|
||||||
|
Description: "Delete recent messages in this channel (max 100)",
|
||||||
|
Options: []*discordgo.ApplicationCommandOption{
|
||||||
|
{
|
||||||
|
Type: discordgo.ApplicationCommandOptionInteger,
|
||||||
|
Name: "amount",
|
||||||
|
Description: "How many recent messages to delete (1-100)",
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: discordgo.ApplicationCommandOptionSubCommand,
|
||||||
|
Name: "timeout",
|
||||||
|
Description: "Temporarily timeout a member",
|
||||||
|
Options: []*discordgo.ApplicationCommandOption{
|
||||||
|
{
|
||||||
|
Type: discordgo.ApplicationCommandOptionUser,
|
||||||
|
Name: "user",
|
||||||
|
Description: "User to timeout",
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: discordgo.ApplicationCommandOptionString,
|
||||||
|
Name: "duration",
|
||||||
|
Description: "Duration like 10m, 1h, 2h30m (max 28d)",
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: discordgo.ApplicationCommandOptionString,
|
||||||
|
Name: "reason",
|
||||||
|
Description: "Optional reason",
|
||||||
|
Required: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: discordgo.ApplicationCommandOptionSubCommand,
|
||||||
|
Name: "kick",
|
||||||
|
Description: "Kick a member from the server",
|
||||||
|
Options: []*discordgo.ApplicationCommandOption{
|
||||||
|
{
|
||||||
|
Type: discordgo.ApplicationCommandOptionUser,
|
||||||
|
Name: "user",
|
||||||
|
Description: "User to kick",
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: discordgo.ApplicationCommandOptionString,
|
||||||
|
Name: "reason",
|
||||||
|
Description: "Optional reason",
|
||||||
|
Required: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: discordgo.ApplicationCommandOptionSubCommand,
|
||||||
|
Name: "ban",
|
||||||
|
Description: "Ban a member from the server",
|
||||||
|
Options: []*discordgo.ApplicationCommandOption{
|
||||||
|
{
|
||||||
|
Type: discordgo.ApplicationCommandOptionUser,
|
||||||
|
Name: "user",
|
||||||
|
Description: "User to ban",
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: discordgo.ApplicationCommandOptionInteger,
|
||||||
|
Name: "delete_days",
|
||||||
|
Description: "Delete message history (0-7 days)",
|
||||||
|
Required: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: discordgo.ApplicationCommandOptionString,
|
||||||
|
Name: "reason",
|
||||||
|
Description: "Optional reason",
|
||||||
|
Required: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: discordgo.ApplicationCommandOptionSubCommand,
|
||||||
|
Name: "unban",
|
||||||
|
Description: "Unban a user by ID",
|
||||||
|
Options: []*discordgo.ApplicationCommandOption{
|
||||||
|
{
|
||||||
|
Type: discordgo.ApplicationCommandOptionString,
|
||||||
|
Name: "user_id",
|
||||||
|
Description: "User ID to unban",
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: discordgo.ApplicationCommandOptionString,
|
||||||
|
Name: "reason",
|
||||||
|
Description: "Optional reason",
|
||||||
|
Required: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: discordgo.ApplicationCommandOptionSubCommand,
|
||||||
|
Name: "untimeout",
|
||||||
|
Description: "Remove timeout from a member",
|
||||||
|
Options: []*discordgo.ApplicationCommandOption{
|
||||||
|
{
|
||||||
|
Type: discordgo.ApplicationCommandOptionUser,
|
||||||
|
Name: "user",
|
||||||
|
Description: "User to untimeout",
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: discordgo.ApplicationCommandOptionString,
|
||||||
|
Name: "reason",
|
||||||
|
Description: "Optional reason",
|
||||||
|
Required: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: discordgo.ApplicationCommandOptionSubCommand,
|
||||||
|
Name: "slowmode",
|
||||||
|
Description: "Set channel slowmode (0 disables)",
|
||||||
|
Options: []*discordgo.ApplicationCommandOption{
|
||||||
|
{
|
||||||
|
Type: discordgo.ApplicationCommandOptionChannel,
|
||||||
|
Name: "channel",
|
||||||
|
Description: "Text channel to update",
|
||||||
|
Required: true,
|
||||||
|
ChannelTypes: []discordgo.ChannelType{discordgo.ChannelTypeGuildText, discordgo.ChannelTypeGuildNews},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: discordgo.ApplicationCommandOptionInteger,
|
||||||
|
Name: "seconds",
|
||||||
|
Description: "Slowmode delay in seconds (0-21600)",
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func ModerationHandler(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||||
|
if i.GuildID == "" {
|
||||||
|
respondEphemeral(s, i, "This command can only be used in a server.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
data := i.ApplicationCommandData()
|
||||||
|
if len(data.Options) == 0 {
|
||||||
|
respondEphemeral(s, i, "Missing subcommand.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch data.Options[0].Name {
|
||||||
|
case "purge":
|
||||||
|
handlePurge(s, i)
|
||||||
|
case "timeout":
|
||||||
|
handleTimeout(s, i)
|
||||||
|
case "kick":
|
||||||
|
handleKick(s, i)
|
||||||
|
case "ban":
|
||||||
|
handleBan(s, i)
|
||||||
|
case "unban":
|
||||||
|
handleUnban(s, i)
|
||||||
|
case "untimeout":
|
||||||
|
handleUntimeout(s, i)
|
||||||
|
case "slowmode":
|
||||||
|
handleSlowmode(s, i)
|
||||||
|
default:
|
||||||
|
respondEphemeral(s, i, "Unknown subcommand.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handlePurge(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||||
|
if !hasPerm(i, discordgo.PermissionManageMessages) {
|
||||||
|
respondEphemeral(s, i, "You need **Manage Messages** for this command.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
opt := i.ApplicationCommandData().Options[0]
|
||||||
|
var amount int64
|
||||||
|
for _, o := range opt.Options {
|
||||||
|
if o.Name == "amount" {
|
||||||
|
amount = o.IntValue()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if amount < 1 || amount > 100 {
|
||||||
|
respondEphemeral(s, i, "Amount must be between 1 and 100.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
msgs, err := s.ChannelMessages(i.ChannelID, int(amount), "", "", "")
|
||||||
|
if err != nil {
|
||||||
|
respondEphemeral(s, i, "Failed to fetch messages.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(msgs) == 0 {
|
||||||
|
respondEphemeral(s, i, "No messages found to delete.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tooOldCutoff := time.Now().Add(-14 * 24 * time.Hour)
|
||||||
|
ids := make([]string, 0, len(msgs))
|
||||||
|
for _, m := range msgs {
|
||||||
|
if m == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if m.Timestamp.Before(tooOldCutoff) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ids = append(ids, m.ID)
|
||||||
|
}
|
||||||
|
if len(ids) == 0 {
|
||||||
|
respondEphemeral(s, i, "No messages can be bulk deleted (older than 14 days).")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(ids) == 1 {
|
||||||
|
if err := s.ChannelMessageDelete(i.ChannelID, ids[0]); err != nil {
|
||||||
|
respondEphemeral(s, i, "Failed to delete message.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
respondEphemeral(s, i, "Deleted 1 message.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.ChannelMessagesBulkDelete(i.ChannelID, ids); err != nil {
|
||||||
|
respondEphemeral(s, i, "Failed to bulk delete messages.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
respondEphemeral(s, i, fmt.Sprintf("Deleted %d messages.", len(ids)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleTimeout(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||||
|
if !hasPerm(i, discordgo.PermissionModerateMembers) {
|
||||||
|
respondEphemeral(s, i, "You need **Moderate Members** for this command.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
opt := i.ApplicationCommandData().Options[0]
|
||||||
|
var userOpt *discordgo.ApplicationCommandInteractionDataOption
|
||||||
|
var durationInput string
|
||||||
|
var reason string
|
||||||
|
for _, o := range opt.Options {
|
||||||
|
switch o.Name {
|
||||||
|
case "user":
|
||||||
|
userOpt = o
|
||||||
|
case "duration":
|
||||||
|
durationInput = strings.TrimSpace(o.StringValue())
|
||||||
|
case "reason":
|
||||||
|
reason = strings.TrimSpace(o.StringValue())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if userOpt == nil {
|
||||||
|
respondEphemeral(s, i, "Missing user option.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
target := userOpt.UserValue(s)
|
||||||
|
if target == nil {
|
||||||
|
respondEphemeral(s, i, "Invalid user.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
dur, err := time.ParseDuration(durationInput)
|
||||||
|
if err != nil || dur <= 0 {
|
||||||
|
respondEphemeral(s, i, "Invalid duration. Examples: `10m`, `1h`, `2h30m`.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
maxTimeout := 28 * 24 * time.Hour
|
||||||
|
if dur > maxTimeout {
|
||||||
|
respondEphemeral(s, i, "Duration cannot exceed 28 days.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
until := time.Now().UTC().Add(dur)
|
||||||
|
if err := s.GuildMemberTimeout(i.GuildID, target.ID, &until); err != nil {
|
||||||
|
respondEphemeral(s, i, "Failed to timeout user. Check role hierarchy and permissions.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if reason == "" {
|
||||||
|
respondEphemeral(s, i, fmt.Sprintf("Timed out %s for `%s`.", target.Mention(), dur.String()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
respondEphemeral(s, i, fmt.Sprintf("Timed out %s for `%s`. Reason: %s", target.Mention(), dur.String(), reason))
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleKick(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||||
|
if !hasPerm(i, discordgo.PermissionKickMembers) {
|
||||||
|
respondEphemeral(s, i, "You need **Kick Members** for this command.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
opt := i.ApplicationCommandData().Options[0]
|
||||||
|
var userOpt *discordgo.ApplicationCommandInteractionDataOption
|
||||||
|
var reason string
|
||||||
|
for _, o := range opt.Options {
|
||||||
|
switch o.Name {
|
||||||
|
case "user":
|
||||||
|
userOpt = o
|
||||||
|
case "reason":
|
||||||
|
reason = strings.TrimSpace(o.StringValue())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if userOpt == nil {
|
||||||
|
respondEphemeral(s, i, "Missing user option.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
target := userOpt.UserValue(s)
|
||||||
|
if target == nil {
|
||||||
|
respondEphemeral(s, i, "Invalid user.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if i.Member != nil && i.Member.User != nil && target.ID == i.Member.User.ID {
|
||||||
|
respondEphemeral(s, i, "You cannot kick yourself.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if s != nil && s.State != nil && s.State.User != nil && target.ID == s.State.User.ID {
|
||||||
|
respondEphemeral(s, i, "I can't kick myself.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.GuildMemberDeleteWithReason(i.GuildID, target.ID, reason); err != nil {
|
||||||
|
respondEphemeral(s, i, "Failed to kick user. Check role hierarchy and permissions.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if reason == "" {
|
||||||
|
respondEphemeral(s, i, "Kicked "+target.Mention()+".")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
respondEphemeral(s, i, "Kicked "+target.Mention()+". Reason: "+reason)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleBan(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||||
|
if !hasPerm(i, discordgo.PermissionBanMembers) {
|
||||||
|
respondEphemeral(s, i, "You need **Ban Members** for this command.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
opt := i.ApplicationCommandData().Options[0]
|
||||||
|
var userOpt *discordgo.ApplicationCommandInteractionDataOption
|
||||||
|
var deleteDays int64
|
||||||
|
var reason string
|
||||||
|
for _, o := range opt.Options {
|
||||||
|
switch o.Name {
|
||||||
|
case "user":
|
||||||
|
userOpt = o
|
||||||
|
case "delete_days":
|
||||||
|
deleteDays = o.IntValue()
|
||||||
|
case "reason":
|
||||||
|
reason = strings.TrimSpace(o.StringValue())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if userOpt == nil {
|
||||||
|
respondEphemeral(s, i, "Missing user option.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
target := userOpt.UserValue(s)
|
||||||
|
if target == nil {
|
||||||
|
respondEphemeral(s, i, "Invalid user.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if deleteDays < 0 || deleteDays > 7 {
|
||||||
|
respondEphemeral(s, i, "delete_days must be between 0 and 7.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if i.Member != nil && i.Member.User != nil && target.ID == i.Member.User.ID {
|
||||||
|
respondEphemeral(s, i, "You cannot ban yourself.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if s != nil && s.State != nil && s.State.User != nil && target.ID == s.State.User.ID {
|
||||||
|
respondEphemeral(s, i, "I can't ban myself.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.GuildBanCreateWithReason(i.GuildID, target.ID, reason, int(deleteDays)); err != nil {
|
||||||
|
respondEphemeral(s, i, "Failed to ban user. Check role hierarchy and permissions.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if reason == "" {
|
||||||
|
respondEphemeral(s, i, fmt.Sprintf("Banned %s (deleted %d days of messages).", target.Mention(), deleteDays))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
respondEphemeral(s, i, fmt.Sprintf("Banned %s (deleted %d days of messages). Reason: %s", target.Mention(), deleteDays, reason))
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleUnban(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||||
|
if !hasPerm(i, discordgo.PermissionBanMembers) {
|
||||||
|
respondEphemeral(s, i, "You need **Ban Members** for this command.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
opt := i.ApplicationCommandData().Options[0]
|
||||||
|
var userID string
|
||||||
|
var reason string
|
||||||
|
for _, o := range opt.Options {
|
||||||
|
switch o.Name {
|
||||||
|
case "user_id":
|
||||||
|
userID = strings.TrimSpace(o.StringValue())
|
||||||
|
case "reason":
|
||||||
|
reason = strings.TrimSpace(o.StringValue())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if userID == "" {
|
||||||
|
respondEphemeral(s, i, "Missing user ID.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.GuildBanDelete(i.GuildID, userID); err != nil {
|
||||||
|
respondEphemeral(s, i, "Failed to unban user. Make sure the user ID is valid and banned.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if reason == "" {
|
||||||
|
respondEphemeral(s, i, "Unbanned user ID `"+userID+"`.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
respondEphemeral(s, i, "Unbanned user ID `"+userID+"`. Reason: "+reason)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleUntimeout(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||||
|
if !hasPerm(i, discordgo.PermissionModerateMembers) {
|
||||||
|
respondEphemeral(s, i, "You need **Moderate Members** for this command.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
opt := i.ApplicationCommandData().Options[0]
|
||||||
|
var userOpt *discordgo.ApplicationCommandInteractionDataOption
|
||||||
|
var reason string
|
||||||
|
for _, o := range opt.Options {
|
||||||
|
switch o.Name {
|
||||||
|
case "user":
|
||||||
|
userOpt = o
|
||||||
|
case "reason":
|
||||||
|
reason = strings.TrimSpace(o.StringValue())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if userOpt == nil {
|
||||||
|
respondEphemeral(s, i, "Missing user option.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
target := userOpt.UserValue(s)
|
||||||
|
if target == nil {
|
||||||
|
respondEphemeral(s, i, "Invalid user.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.GuildMemberTimeout(i.GuildID, target.ID, nil); err != nil {
|
||||||
|
respondEphemeral(s, i, "Failed to remove timeout. Check role hierarchy and permissions.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if reason == "" {
|
||||||
|
respondEphemeral(s, i, "Removed timeout from "+target.Mention()+".")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
respondEphemeral(s, i, "Removed timeout from "+target.Mention()+". Reason: "+reason)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleSlowmode(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||||
|
if !hasPerm(i, discordgo.PermissionManageChannels) {
|
||||||
|
respondEphemeral(s, i, "You need **Manage Channels** for this command.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
opt := i.ApplicationCommandData().Options[0]
|
||||||
|
var chOpt *discordgo.ApplicationCommandInteractionDataOption
|
||||||
|
var seconds int64
|
||||||
|
for _, o := range opt.Options {
|
||||||
|
switch o.Name {
|
||||||
|
case "channel":
|
||||||
|
chOpt = o
|
||||||
|
case "seconds":
|
||||||
|
seconds = o.IntValue()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if chOpt == nil {
|
||||||
|
respondEphemeral(s, i, "Missing channel option.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if seconds < 0 || seconds > 21600 {
|
||||||
|
respondEphemeral(s, i, "Seconds must be between 0 and 21600.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ch := chOpt.ChannelValue(s)
|
||||||
|
if ch == nil {
|
||||||
|
respondEphemeral(s, i, "Invalid channel.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
secondsInt := int(seconds)
|
||||||
|
_, err := s.ChannelEditComplex(ch.ID, &discordgo.ChannelEdit{
|
||||||
|
RateLimitPerUser: &secondsInt,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
respondEphemeral(s, i, "Failed to update slowmode.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if seconds == 0 {
|
||||||
|
respondEphemeral(s, i, "Disabled slowmode in "+ch.Mention()+".")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
respondEphemeral(s, i, fmt.Sprintf("Set slowmode in %s to %d seconds.", ch.Mention(), seconds))
|
||||||
|
}
|
||||||
|
|
||||||
|
func hasPerm(i *discordgo.InteractionCreate, perm int64) bool {
|
||||||
|
return i.Member != nil && (i.Member.Permissions&perm) == perm
|
||||||
|
}
|
||||||
|
|
||||||
|
func respondEphemeral(s *discordgo.Session, i *discordgo.InteractionCreate, msg string) {
|
||||||
|
_ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||||
|
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||||
|
Data: &discordgo.InteractionResponseData{
|
||||||
|
Content: msg,
|
||||||
|
Flags: discordgo.MessageFlagsEphemeral,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package moderation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"velox-bot/internal/commands/moderation/public"
|
||||||
|
|
||||||
|
"github.com/bwmarrin/discordgo"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
Moderation *discordgo.ApplicationCommand = public.Moderation
|
||||||
|
)
|
||||||
|
|
||||||
|
func ModerationHandler(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||||
|
public.ModerationHandler(s, i)
|
||||||
|
}
|
||||||
|
|
||||||
Reference in New Issue
Block a user