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:
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.")
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user