Skip to content
This repository was archived by the owner on Dec 1, 2021. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package net.javadiscord.javabot2.command;

import lombok.Getter;
import org.javacord.api.interaction.callback.InteractionImmediateResponseBuilder;

import java.util.function.Supplier;

/**
* An exception which can be thrown so that the bot can respond with a well
* formatted error or warning response, while still allowing you to take
* advantage of throwing an exception up the call stack.
*/
public class ResponseException extends Exception {
@Getter
private final InteractionImmediateResponseBuilder responseBuilder;

public ResponseException(InteractionImmediateResponseBuilder responseBuilder) {
this.responseBuilder = responseBuilder;
}

public static Supplier<ResponseException> warning(String message) {
return () -> new ResponseException(Responses.deferredWarningBuilder().message(message).build());
}

public static Supplier<ResponseException> error(String message) {
return () -> new ResponseException(Responses.deferredErrorBuilder().message(message).build());
}
}
148 changes: 148 additions & 0 deletions src/main/java/net/javadiscord/javabot2/command/Responses.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
package net.javadiscord.javabot2.command;

import org.javacord.api.entity.message.MessageFlag;
import org.javacord.api.entity.message.embed.EmbedBuilder;
import org.javacord.api.event.interaction.SlashCommandCreateEvent;
import org.javacord.api.interaction.InteractionBase;
import org.javacord.api.interaction.callback.InteractionImmediateResponseBuilder;
import org.javacord.api.interaction.callback.InteractionOriginalResponseUpdater;

import java.awt.*;
import java.util.concurrent.CompletableFuture;

/**
* Provides methods for standardized replies to interaction events.
*/
public class Responses {
public static InteractionImmediateResponseBuilder success(InteractionBase interaction, String title, String message) {
return reply(interaction, title, message, Color.GREEN, true);
}

public static ResponseBuilder successBuilder(InteractionBase interaction) {
return new ResponseBuilder(interaction, Color.GREEN).title("Success");
}

public static ResponseBuilder successBuilder(SlashCommandCreateEvent event) {
return successBuilder(event.getSlashCommandInteraction());
}

public static InteractionImmediateResponseBuilder info(InteractionBase interaction, String title, String message) {
return reply(interaction, title, message, Color.BLUE, true);
}

public static ResponseBuilder infoBuilder(InteractionBase interaction) {
return new ResponseBuilder(interaction, Color.BLUE).title("Info");
}

public static ResponseBuilder infoBuilder(SlashCommandCreateEvent event) {
return infoBuilder(event.getSlashCommandInteraction());
}

public static InteractionImmediateResponseBuilder warning(InteractionBase interaction, String title, String message) {
return reply(interaction, title, message, Color.ORANGE, true);
}

public static InteractionImmediateResponseBuilder warning(InteractionBase interaction, String message) {
return warning(interaction, "Warning", message);
}

public static ResponseBuilder warningBuilder(InteractionBase interaction) {
return new ResponseBuilder(interaction, Color.ORANGE).title("Warning");
}

public static ResponseBuilder warningBuilder(SlashCommandCreateEvent event) {
return warningBuilder(event.getSlashCommandInteraction());
}

public static ResponseBuilder deferredWarningBuilder() {
return new ResponseBuilder(Color.ORANGE).title("Warning");
}

public static InteractionImmediateResponseBuilder error(InteractionBase interaction, String message) {
return reply(interaction, "An Error Occurred", message, Color.RED, true);
}

public static ResponseBuilder errorBuilder(InteractionBase interaction) {
return new ResponseBuilder(interaction, Color.RED).title("An Error Occurred");
}

public static ResponseBuilder errorBuilder(SlashCommandCreateEvent event) {
return errorBuilder(event.getSlashCommandInteraction());
}

public static ResponseBuilder deferredErrorBuilder() {
return new ResponseBuilder(Color.RED).title("An Error Occurred");
}

private static InteractionImmediateResponseBuilder reply(
InteractionBase interaction,
String title,
String message,
Color color,
boolean ephemeral
) {
var responder = interaction.createImmediateResponder()
.addEmbed(new EmbedBuilder()
.setTitle(title)
.setColor(color)
.setTimestampToNow()
.setDescription(message));
if (ephemeral) {
responder.setFlags(MessageFlag.EPHEMERAL);
}
return responder;
}

/**
* A builder that's used to construct a response using a fluent interface.
*/
public static class ResponseBuilder {
private InteractionBase interaction;
private String title = null;
private String message = null;
private final Color color;
private boolean ephemeral = true;

private ResponseBuilder(InteractionBase interaction, Color color) {
this.interaction = interaction;
this.color = color;
}

private ResponseBuilder(Color color) {
this(null, color);
}

public ResponseBuilder title(String title) {
this.title = title;
return this;
}

public ResponseBuilder message(String message) {
this.message = message;
return this;
}

public ResponseBuilder makePublic() {
this.ephemeral = false;
return this;
}

public InteractionImmediateResponseBuilder build() {
return reply(interaction, title, message, color, ephemeral);
}

public InteractionImmediateResponseBuilder build(InteractionBase interaction) {
this.interaction = interaction;
return build();
}

public CompletableFuture<InteractionOriginalResponseUpdater> respond(InteractionBase interaction) {
this.interaction = interaction;
return build().respond();
}

public CompletableFuture<InteractionOriginalResponseUpdater> respond() {
return this.build().respond();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
import org.javacord.api.interaction.SlashCommandInteraction;
import org.javacord.api.interaction.callback.InteractionImmediateResponseBuilder;

/**
* An interface that should be implemented by any class that is defined as a
* handler in any command configuration file.
*/
public interface SlashCommandHandler {
InteractionImmediateResponseBuilder handle(SlashCommandInteraction interaction) throws Exception;
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package net.javadiscord.javabot2.command;

import lombok.extern.slf4j.Slf4j;
import net.javadiscord.javabot2.command.data.CommandConfig;
import net.javadiscord.javabot2.command.data.CommandDataLoader;
import org.javacord.api.DiscordApi;
import org.javacord.api.entity.message.MessageFlag;
import org.javacord.api.event.interaction.SlashCommandCreateEvent;
import org.javacord.api.interaction.ServerSlashCommandPermissionsBuilder;
import org.javacord.api.interaction.SlashCommandBuilder;
Expand All @@ -21,6 +22,13 @@
public final class SlashCommandListener implements SlashCommandCreateListener {
private final Map<Long, SlashCommandHandler> commandHandlers = new HashMap<>();

/**
* Constructs a new slash command listener using the given Discord api, and
* loads commands from configuration YAML files according to the list of
* resources.
* @param api The Discord api to use.
* @param resources The list of classpath resources to load commands from.
*/
public SlashCommandListener(DiscordApi api, String... resources) {
registerSlashCommands(api, resources)
.thenAcceptAsync(commandHandlers::putAll);
Expand All @@ -32,17 +40,18 @@ public void onSlashCommandCreate(SlashCommandCreateEvent event) {
if (handler != null) {
try {
handler.handle(event.getSlashCommandInteraction()).respond();
} catch (ResponseException e) {
e.getResponseBuilder().respond();
} catch (Exception e) {
log.error("An error occurred while handling a slash command.", e);
event.getSlashCommandInteraction().createImmediateResponder()
.setFlags(MessageFlag.EPHEMERAL)
.append("An error occurred.")
Responses.errorBuilder(event)
.message("An error occurred while executing the command.")
.respond();
}
} else {
event.getSlashCommandInteraction().createImmediateResponder()
.setFlags(MessageFlag.EPHEMERAL)
.append("There is no associated handler for this command. Please contact an administrator if this error persists.")
Responses.warningBuilder(event)
.title("No Handler")
.message("There is no associated handler for this command. Please contact an administrator if this error persists.")
.respond();
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package net.javadiscord.javabot2.command;
package net.javadiscord.javabot2.command.data;

import lombok.Data;
import org.javacord.api.interaction.SlashCommandBuilder;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package net.javadiscord.javabot2.command;
package net.javadiscord.javabot2.command.data;

import org.yaml.snakeyaml.Yaml;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package net.javadiscord.javabot2.command;
package net.javadiscord.javabot2.command.data;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package net.javadiscord.javabot2.command;
package net.javadiscord.javabot2.command.data;

import lombok.Data;
import org.javacord.api.interaction.SlashCommandOptionBuilder;
import org.javacord.api.interaction.SlashCommandOptionChoiceBuilder;
import org.javacord.api.interaction.SlashCommandOptionType;

/**
Expand All @@ -16,12 +17,11 @@ public class OptionConfig {
private boolean required;

public SlashCommandOptionBuilder toData() {
var builder = new SlashCommandOptionBuilder()
return new SlashCommandOptionBuilder()
.setType(SlashCommandOptionType.valueOf(this.type.toUpperCase()))
.setName(this.name)
.setDescription(this.description)
.setRequired(this.required);
return builder;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package net.javadiscord.javabot2.command;
package net.javadiscord.javabot2.command.data;

import lombok.Data;
import org.javacord.api.interaction.SlashCommandOptionBuilder;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package net.javadiscord.javabot2.command;
package net.javadiscord.javabot2.command.data;

import lombok.Data;
import org.javacord.api.interaction.SlashCommandOptionBuilder;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,36 @@
package net.javadiscord.javabot2.systems.moderation;

import lombok.extern.slf4j.Slf4j;
import net.javadiscord.javabot2.Bot;
import net.javadiscord.javabot2.command.ResponseException;
import net.javadiscord.javabot2.command.Responses;
import net.javadiscord.javabot2.command.SlashCommandHandler;
import org.javacord.api.entity.message.Message;
import org.javacord.api.entity.user.User;
import org.javacord.api.interaction.SlashCommandInteraction;
import org.javacord.api.interaction.callback.InteractionImmediateResponseBuilder;

@Slf4j
public class PurgeCommand implements SlashCommandHandler {
@Override
public InteractionImmediateResponseBuilder handle(SlashCommandInteraction interaction) {
return interaction.createImmediateResponder()
.append("Not yet implemented");
public InteractionImmediateResponseBuilder handle(SlashCommandInteraction interaction) throws Exception {
var until = interaction.getOptionStringValueByName("until")
.orElseThrow(ResponseException.warning("Missing required parameter."));
var userOption = interaction.getOptionUserValueByName("user");
var channel = interaction.getChannel()
.orElseThrow(ResponseException.warning("This command can only be used in text channels."));
channel.getMessageById(until).thenAcceptAsync(message -> Bot.asyncPool.submit(() -> purge(message, userOption.orElse(null))));
return Responses.info(interaction, "Purge Started", "Messages will be deleted!");
}

private void purge(Message until, User user) {
log.info("Purging all messages in {} until {}.", until.getServerTextChannel().orElseThrow().getName(), until.getId());
until.getMessagesAfterAsStream()
.filter(message -> (user == null || message.getAuthor().getId() == user.getId()))
.forEach(message -> message.delete().join());
if (user == null || until.getAuthor().getId() == user.getId()) {
until.delete().join();
}
log.info("Purge completed.");
}
}
14 changes: 5 additions & 9 deletions src/main/resources/commands/moderation.yaml
Original file line number Diff line number Diff line change
@@ -1,20 +1,16 @@
- name: purge
description: Deletes messages from a channel.
handler: net.javadiscord.javabot2.systems.moderation.PruneCommand
handler: net.javadiscord.javabot2.systems.moderation.PurgeCommand
enabledByDefault: false
privileges:
- type: ROLE
id: moderation.staffRoleId
options:
- name: amount
description: Number of messages to remove. If left blank, all messages will be removed.
type: INTEGER
required: false
- name: until
description: All messages from now up to (and including) the given message id will be removed.
type: STRING
required: true
- name: user
description: The user whose messages to remove. If left blank, messages from any user are removed.
type: USER
required: false
- name: archive
description: If true, save removed messages in an archive.
type: BOOLEAN
required: false