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:`.") 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 \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) }