Skip to content

Commit

Permalink
fix: Refactored and combined prune command w/ dry run into one comman…
Browse files Browse the repository at this point in the history
…d. (#37)

Previously, there were two separate commands for pruning. One that
actually pruned members. And one which was used as a dry run, in order
to see which members would be pruned. This has now been combined into
one command, with a dry run flag that is true by default.
  • Loading branch information
myrkvi authored Jan 17, 2025
2 parents 7b2c3a6 + d61d83a commit dc87a27
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 116 deletions.
176 changes: 62 additions & 114 deletions commands/prune.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,95 +15,6 @@ import (
"github.com/myrkvi/heimdallr/utils"
)

var PruneDryRunCommand = discord.SlashCommandCreate{
Name: "prune-pending-members-dry-run",
NameLocalizations: map[discord.Locale]string{
discord.LocaleNorwegian: "fjern-ventende-medlemmer-dry-run",
},
Description: "Prune members.",
DescriptionLocalizations: map[discord.Locale]string{
discord.LocaleNorwegian: "Fjern medlemmer.",
},

Contexts: []discord.InteractionContextType{
discord.InteractionContextTypeGuild,
},
IntegrationTypes: []discord.ApplicationIntegrationType{
discord.ApplicationIntegrationTypeGuildInstall,
},

DefaultMemberPermissions: json.NewNullablePtr(discord.PermissionManageGuild),

Options: []discord.ApplicationCommandOption{
discord.ApplicationCommandOptionInt{
Name: "days",
NameLocalizations: map[discord.Locale]string{
discord.LocaleNorwegian: "dager",
},
Description: "The number of days to prune members for.",
DescriptionLocalizations: map[discord.Locale]string{
discord.LocaleNorwegian: "Antall dager å fjerne medlemmer for.",
},
Required: true,

MinValue: utils.Ref(0),
MaxValue: utils.Ref(90),
},
},
}

func PruneDryRunHandler(e *handler.CommandEvent) error {
if e.GuildID() == nil {
return ErrEventNoGuildID
}
days := e.SlashCommandInteractionData().Int("days")

guildSettings, err := model.GetGuildSettings(*e.GuildID())
if err != nil {
_ = e.CreateMessage(discord.NewMessageCreateBuilder().
SetEphemeral(true).
SetContent("Failed to prune members: could not get guild settings.").
Build())
return err
}

if guildSettings.GatekeepPendingRole == 0 {
return e.CreateMessage(discord.NewMessageCreateBuilder().
SetEphemeral(true).
SetContent("Failed to prune members: no pending role set. This command will only prune pending members.").
Build())
}

_ = e.DeferCreateMessage(true)

prunableMembers, err := getPrunableMembers(e, days, guildSettings)
if err != nil {
_, err = e.CreateFollowupMessage(discord.NewMessageCreateBuilder().
SetEphemeral(true).
SetContent("Failed to prune members: could not get member list.").
Build())
return err
}

numKicked := len(prunableMembers)

adminMessage := fmt.Sprintf("Dry run: pruned %d members.\n\nMembers:\n", numKicked)

for _, member := range prunableMembers {
if member == nil {
continue
}

adminMessage += fmt.Sprintf("-# %s (%s)\n", member.User.Username, member.User.ID)
}

_, err = e.CreateFollowupMessage(discord.NewMessageCreateBuilder().
SetEphemeral(true).
SetContent(adminMessage).
Build())
return err
}

var PruneCommand = discord.SlashCommandCreate{
Name: "prune-pending-members",
NameLocalizations: map[discord.Locale]string{
Expand Down Expand Up @@ -138,6 +49,10 @@ var PruneCommand = discord.SlashCommandCreate{
MinValue: utils.Ref(3),
MaxValue: utils.Ref(90),
},
discord.ApplicationCommandOptionBool{
Name: "dry-run",
Description: "If true, will show members that would be pruned without actually pruning them. (Default: true)",
},
},
}

Expand All @@ -146,6 +61,10 @@ func PruneHandler(e *handler.CommandEvent) error {
return ErrEventNoGuildID
}
days := e.SlashCommandInteractionData().Int("days")
dryRun, dryRunOk := e.SlashCommandInteractionData().OptBool("dry-run")
if !dryRunOk {
dryRun = true
}

guildSettings, err := model.GetGuildSettings(*e.GuildID())
if err != nil {
Expand All @@ -164,9 +83,6 @@ func PruneHandler(e *handler.CommandEvent) error {
}

_ = e.DeferCreateMessage(true)

var kickedMembers []*discord.Member

prunableMembers, err := getPrunableMembers(e, days, guildSettings)
if err != nil {
_, err = e.CreateFollowupMessage(discord.NewMessageCreateBuilder().
Expand All @@ -176,41 +92,36 @@ func PruneHandler(e *handler.CommandEvent) error {
return err
}

for _, member := range prunableMembers {
if dryRun {
err = dryRunPruneMembers(e, prunableMembers)
} else {
err = pruneMembers(e, *guildSettings, prunableMembers)
}

globals.ExcludedFromModKickLog[member.User.ID] = struct{}{}
kickedMembers = append(kickedMembers, member)
if err != nil {
slog.Error("Failed to prune members.", "dry_run", dryRun, "err", err)
}
return err
}

err = e.Client().Rest().RemoveMember(*e.GuildID(), member.User.ID,
rest.WithReason(
fmt.Sprintf("User pruned with command. Pruned by: %s (%s)",
e.User().Username, e.User().ID)))
if err != nil {
slog.Error("Failed to prune member.", "err", err, "user_id", member.User.ID)
_, err = e.CreateFollowupMessage(discord.NewMessageCreateBuilder().
SetEphemeral(true).
SetContent("Failed to prune members: failed to remove member.").
Build())
return err
}
func pruneMembers(e *handler.CommandEvent, guildSettings model.GuildSettings, members []*discord.Member) error {
kickedMembers, err := kickMembers(e, members)
if err != nil {
return err
}

numKicked := len(kickedMembers)

adminMessage := fmt.Sprintf("Pruned %d members.\n\nMembers:\n", numKicked)

adminMessage := fmt.Sprintf("Pruned %d members.\n\nMembers: \n", numKicked)
for _, member := range kickedMembers {
if member == nil {
continue
}

adminMessage += fmt.Sprintf("-# %s (%s)\n", member.User.Username, member.User.ID)
}

if numKicked > 0 && guildSettings.ModeratorChannel != 0 {
_, err = e.Client().Rest().CreateMessage(guildSettings.ModeratorChannel, discord.NewMessageCreateBuilder().
SetContent(adminMessage).
SetEphemeral(true).
Build())
if err != nil {
slog.Error("Failed to send prune message to moderator channel.",
Expand All @@ -221,15 +132,52 @@ func PruneHandler(e *handler.CommandEvent) error {
}
}

_ = days

_, err = e.CreateFollowupMessage(discord.NewMessageCreateBuilder().
SetEphemeral(true).
SetContentf("Pruned %d users.", numKicked).
Build())
return err
}

func dryRunPruneMembers(e *handler.CommandEvent, members []*discord.Member) error {
numKicked := len(members)
adminMessage := fmt.Sprintf("Dry run: Pruned %d members.\n\nMembers:\n", numKicked)
for _, member := range members {
if member == nil {
continue
}
adminMessage += fmt.Sprintf("-# %s (%s)\n", member.User.Username, member.User.ID)
}

_, err := e.CreateFollowupMessage(discord.NewMessageCreateBuilder().
SetEphemeral(true).
SetContent(adminMessage).
Build())
return err
}

func kickMembers(e *handler.CommandEvent, members []*discord.Member) (kickedMembers []*discord.Member, err error) {
for _, member := range members {
if member == nil {
slog.Warn("Member is nil.")
continue
}
globals.ExcludedFromModKickLog[member.User.ID] = struct{}{}

err = e.Client().Rest().RemoveMember(member.GuildID, member.User.ID,
rest.WithReason(
fmt.Sprintf("User pruned with command. Pruned by %s (%s)",
e.User().Username, e.User().ID)),
)
if err != nil {
slog.Error("Failed to prune member.", "err", err, "user_id", member.User.ID)
return
}
kickedMembers = append(kickedMembers, member)
}
return
}

func getPrunableMembers(e *handler.CommandEvent, days int, guildSettings *model.GuildSettings) (members []*discord.Member, err error) {
maxTimeDiff := time.Duration(days) * time.Hour * 24

Expand Down
2 changes: 0 additions & 2 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,6 @@ func main() {
r.Component("/role/assign/{roleID}", components.RoleAssignButtonHandler)

r.Command("/prune-pending-members", commands.PruneHandler)
r.Command("/prune-pending-members-dry-run", commands.PruneDryRunHandler)

commandCreates := []discord.ApplicationCommandCreate{
commands.PingCommand,
Expand All @@ -131,7 +130,6 @@ func main() {
commands.ApproveUserCommand,
commands.CreateRoleButtonCommand,
commands.PruneCommand,
commands.PruneDryRunCommand,
}

client, err := disgo.New(token,
Expand Down

0 comments on commit dc87a27

Please sign in to comment.