Commands for everything a dashboard can do

- /music skip, pause, resume, stop, repeat, remove, move (DJ role)
- /config view, clear, toggletwitch, streamers, beta-features (server owner)
- /slvl status, remove-reward; /moderation bans, timeouts; /security history
- /beta is now the bot owner's DM-only command: set, off, servers, remove-server, status
- betasettings service and repo methods, with integration tests

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
This commit is contained in:
FernandoJVideiraandClaude Sonnet 5.5 committed 2026-10-23 02:10:05 +01:00
1 parent acdb5ead35
commit 132a2cd986
26 files changed
+1062 -65

No files matched your search

@@ -0,0 +1,106 @@
package public
import (
"context"
"fmt"
"strings"
"time"
"github.com/bwmarrin/discordgo"
)
// Looking at who is banned and who is timed out, so unbanning and ending a
// timeout never needs a dashboard to find the person first.
func listOptions() []*discordgo.ApplicationCommandOption {
return []*discordgo.ApplicationCommandOption{
{Type: discordgo.ApplicationCommandOptionSubCommand, Name: "bans", Description: "List banned users, to use with /moderation unban"},
{Type: discordgo.ApplicationCommandOptionSubCommand, Name: "timeouts", Description: "List members who are timed out right now"},
}
}
const listShown = 20
func handleBans(s *discordgo.Session, i *discordgo.InteractionCreate) {
if !hasPerm(i, discordgo.PermissionBanMembers) {
respondEphemeral(s, i, "You need **Ban Members** for this command.")
return
}
bans, err := s.GuildBans(i.GuildID, 1000, "", "")
if err != nil {
respondEphemeral(s, i, "I couldn't read the ban list. I need the **Ban Members** permission.")
return
}
if len(bans) == 0 {
respondEphemeral(s, i, "Nobody is banned.")
return
}
var b strings.Builder
fmt.Fprintf(&b, "**Banned users (%d)**\n", len(bans))
for n, ban := range bans {
if n == listShown {
fmt.Fprintf(&b, "…and %d more.\n", len(bans)-listShown)
break
}
name := "unknown"
id := ""
if ban.User != nil {
name, id = ban.User.Username, ban.User.ID
}
reason := strings.TrimSpace(ban.Reason)
if reason == "" {
reason = "no reason"
}
fmt.Fprintf(&b, "• %s (`%s`): %s\n", escape(name), id, escape(clip(reason, 80)))
}
b.WriteString("Unban with `/moderation unban user_id:<id>`.")
respondEphemeral(s, i, clip(b.String(), 1950))
}
func handleTimeouts(s *discordgo.Session, i *discordgo.InteractionCreate) {
if !hasPerm(i, discordgo.PermissionModerateMembers) {
respondEphemeral(s, i, "You need **Moderate Members** for this command.")
return
}
_, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
members, err := s.GuildMembers(i.GuildID, "", 1000)
if err != nil {
respondEphemeral(s, i, "I couldn't read the member list.")
return
}
now := time.Now()
var out []*discordgo.Member
for _, m := range members {
if m.CommunicationDisabledUntil != nil && m.CommunicationDisabledUntil.After(now) {
out = append(out, m)
}
}
if len(out) == 0 {
respondEphemeral(s, i, "Nobody is timed out (among the first 1000 members).")
return
}
var b strings.Builder
fmt.Fprintf(&b, "**Timed out (%d)**\n", len(out))
for n, m := range out {
if n == listShown {
fmt.Fprintf(&b, "…and %d more.\n", len(out)-listShown)
break
}
fmt.Fprintf(&b, "• <@%s> until <t:%d:f>\n", m.User.ID, m.CommunicationDisabledUntil.Unix())
}
b.WriteString("End one with `/moderation untimeout`.")
respondEphemeral(s, i, clip(b.String(), 1950))
}
func clip(s string, n int) string {
if r := []rune(s); len(r) > n {
return string(r[:n-1]) + "…"
}
return s
}
// escape keeps names and reasons from becoming markdown or mentions.
func escape(s string) string {
return strings.NewReplacer("`", "'", "*", "", "_", " ", "~", "", "@", "@​", "|", "/").Replace(s)
}
@@ -14,7 +14,7 @@ import (
var Moderation = &discordgo.ApplicationCommand{
Name: "moderation",
Description: "High-value moderation tools",
Options: []*discordgo.ApplicationCommandOption{
Options: append([]*discordgo.ApplicationCommandOption{
{
Type: discordgo.ApplicationCommandOptionSubCommand,
Name: "purge",
@@ -155,7 +155,7 @@ var Moderation = &discordgo.ApplicationCommand{
},
},
},
},
}, listOptions()...),
}
func ModerationHandler(s *discordgo.Session, i *discordgo.InteractionCreate) {
@@ -185,6 +185,10 @@ func ModerationHandler(s *discordgo.Session, i *discordgo.InteractionCreate) {
handleUntimeout(s, i)
case "slowmode":
handleSlowmode(s, i)
case "bans":
handleBans(s, i)
case "timeouts":
handleTimeouts(s, i)
default:
respondEphemeral(s, i, "Unknown subcommand.")
}