Merge branch 'master' into seenCommand

# Conflicts:
#	src/main/java/com/alttd/commandManager/CommandManager.java
This commit is contained in:
Teriuihi 2022-09-29 19:06:30 +02:00
commit 2d652bc780
36 changed files with 1971 additions and 108 deletions

View File

@ -38,27 +38,23 @@ tasks {
}
}
// create<ConfigureShadowRelocation>("relocateJars") {
// target = shadowJar.get()
// prefix = "${project.name}.lib"
// }
//
// shadowJar {
// dependsOn(getByName("relocateJars") as ConfigureShadowRelocation)
// archiveFileName.set("${project.name}-${project.version}.jar")
// minimize()
// configurations = listOf(project.configurations.shadow.get())
// }
//
shadowJar {
archiveFileName.set(rootProject.name + ".jar")
}
build {
dependsOn(shadowJar)
}
jar {
enabled = false
}
}
dependencies {
// JDA
implementation("net.dv8tion:JDA:5.0.0-alpha.19") {
implementation("net.dv8tion:JDA:5.0.0-alpha.20") {
exclude("opus-java") // exclude audio
}
// MySQL
@ -67,4 +63,8 @@ dependencies {
// Configurate
implementation("org.spongepowered:configurate-yaml:4.1.2")
compileOnly("org.projectlombok:lombok:1.18.24")
annotationProcessor("org.projectlombok:lombok:1.18.24")
}

34
example.requests.yml Normal file
View File

@ -0,0 +1,34 @@
request:
guild: '776590138296893480'
category: '776590138296893481'
channel: '1017787342561476709'
message: '1017839462111256667'
types:
bug-report:
category: '776590138296893481'
channel: '820222354180800525'
name: Bug report
title: What should the title be?
description: Report a new bug.
message: Describe the bug.
act-request:
category: '776590138296893481'
channel: '820222354180800525'
name: Act request
title: What should the title be?
description: Make a new act request.
message: Describe the act request.
admin-act-request:
category: '776590138296893481'
channel: '820222354180800525'
name: Admin act request
title: What should the title be?
description: Make a new admin act request.
message: Describe the admin act request.
feature-request:
category: '776590138296893481'
channel: '820222354180800525'
name: Feature request
title: What should the title be?
description: Make a new feature request.
message: Describe the feature request.

View File

@ -30,8 +30,8 @@ public class AltitudeBot {
private void start() {
Logger.info("Starting bot...");
initConfigs();
ConsoleCommandManager.startConsoleCommands(jda);
jda = JDABuilder.createDefault(SettingsConfig.TOKEN).build();
ConsoleCommandManager.startConsoleCommands(jda);
DatabaseTables.createTables(Database.getDatabase().getConnection());
// try {
// jda.getPresence().setPresence(
@ -41,7 +41,6 @@ public class AltitudeBot {
// Logger.exception(e);
// }
initListeners();
//TODO init permissionManager
}
private void initListeners() {

View File

@ -1,5 +1,7 @@
package com.alttd.buttonManager;
import com.alttd.buttonManager.buttons.remindMeConfirm.ButtonRemindMeCancel;
import com.alttd.buttonManager.buttons.remindMeConfirm.ButtonRemindMeConfirm;
import com.alttd.buttonManager.buttons.suggestionReview.ButtonSuggestionReviewAccept;
import com.alttd.buttonManager.buttons.suggestionReview.ButtonSuggestionReviewDeny;
import com.alttd.util.Util;
@ -22,7 +24,9 @@ public class ButtonManager extends ListenerAdapter {
public ButtonManager() {
buttons = List.of(
new ButtonSuggestionReviewAccept(),
new ButtonSuggestionReviewDeny());
new ButtonSuggestionReviewDeny(),
new ButtonRemindMeCancel(),
new ButtonRemindMeConfirm());
}
@Override

View File

@ -0,0 +1,36 @@
package com.alttd.buttonManager.buttons.remindMeConfirm;
import com.alttd.buttonManager.DiscordButton;
import com.alttd.database.queries.QueriesReminders.Reminder;
import com.alttd.util.Util;
import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent;
import net.dv8tion.jda.api.interactions.components.buttons.Button;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class ButtonRemindMeCancel extends DiscordButton {
@Override
public String getButtonId() {
return "remind_me_cancel";
}
@Override
public void execute(ButtonInteractionEvent event) {
HookAndReminder hookAndReminder = ButtonRemindMeConfirm.removeReminder(event.getUser().getIdLong());
if (hookAndReminder == null) {
event.replyEmbeds(Util.genericErrorEmbed("Error", "Your reminder was already cancelled!"))
.setEphemeral(true).queue();
return;
}
event.replyEmbeds(Util.genericSuccessEmbed("Success", "Cancelled your reminder!"))
.setEphemeral(true).queue();
hookAndReminder.interactionHook().editOriginalComponents(List.of()).queue();
}
@Override
public Button getButton() {
return Button.danger(getButtonId(), "Cancel");
}
}

View File

@ -0,0 +1,84 @@
package com.alttd.buttonManager.buttons.remindMeConfirm;
import com.alttd.buttonManager.DiscordButton;
import com.alttd.database.queries.QueriesReminders.QueriesReminders;
import com.alttd.database.queries.QueriesReminders.Reminder;
import com.alttd.reminders.ReminderScheduler;
import com.alttd.util.Util;
import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent;
import net.dv8tion.jda.api.interactions.InteractionHook;
import net.dv8tion.jda.api.interactions.components.buttons.Button;
import java.util.HashMap;
import java.util.List;
public class ButtonRemindMeConfirm extends DiscordButton {
private static final HashMap<Long, HookAndReminder> unconfirmedReminders = new HashMap<>();
public static synchronized void putReminder(long id, InteractionHook defer, Reminder reminder) {
unconfirmedReminders.put(id, new HookAndReminder(reminder, defer));
}
public static synchronized HookAndReminder removeReminder(long id) {
return unconfirmedReminders.remove(id);
}
@Override
public String getButtonId() {
return "remind_me_confirm";
}
@Override
public void execute(ButtonInteractionEvent event) {
HookAndReminder hookAndReminder = removeReminder(event.getUser().getIdLong());
if (storeReminder(hookAndReminder.reminder(), event)) {
event.replyEmbeds(Util.genericSuccessEmbed("Success", "Your reminder was successfully created!"))
.setEphemeral(true).queue();
}
hookAndReminder.interactionHook().editOriginalComponents(List.of()).queue();
}
private boolean storeReminder(Reminder reminder, ButtonInteractionEvent event) {
if (reminder == null) {
event.replyEmbeds(Util.genericErrorEmbed("Error", "Unable to retrieve reminder data for this button"))
.setEphemeral(true).queue();
return false;
}
int id = QueriesReminders.storeReminder(reminder);
if (id == 0) {
event.replyEmbeds(Util.genericErrorEmbed("Error", "Unable to store reminder in the database"))
.setEphemeral(true).queue();
return false;
}
reminder = new Reminder(
id,
reminder.title(),
reminder.description(),
reminder.userId(),
reminder.guildId(),
reminder.channelId(),
reminder.messageId(),
reminder.shouldRepeat(),
reminder.creationDate(),
reminder.remindDate());
ReminderScheduler instance = ReminderScheduler.getInstance(event.getJDA());
if (instance == null) {
QueriesReminders.removeReminder(reminder.id());
event.replyEmbeds(Util.genericErrorEmbed("Error", "Unable to start reminder, removing it from the database..."))
.setEphemeral(true).queue();
return false;
}
instance.addReminder(reminder);
return true;
}
@Override
public Button getButton() {
return Button.success(getButtonId(), "Confirm");
}
}

View File

@ -0,0 +1,8 @@
package com.alttd.buttonManager.buttons.remindMeConfirm;
import com.alttd.database.queries.QueriesReminders.Reminder;
import net.dv8tion.jda.api.interactions.InteractionHook;
record HookAndReminder(Reminder reminder, InteractionHook interactionHook) {
}

View File

@ -8,10 +8,15 @@ import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.MessageEmbed;
import net.dv8tion.jda.api.entities.channel.middleman.GuildMessageChannel;
import net.dv8tion.jda.api.entities.channel.concrete.ForumChannel;
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
import net.dv8tion.jda.api.entities.channel.middleman.GuildChannel;
import net.dv8tion.jda.api.entities.emoji.Emoji;
import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent;
import net.dv8tion.jda.api.interactions.components.buttons.Button;
import net.dv8tion.jda.api.requests.RestAction;
import net.dv8tion.jda.api.utils.messages.MessageCreateBuilder;
import net.dv8tion.jda.api.utils.messages.MessageCreateData;
import java.awt.*;
import java.util.List;
@ -26,16 +31,6 @@ public class ButtonSuggestionReviewAccept extends DiscordButton {
@Override
public void execute(ButtonInteractionEvent event) {
Message message = event.getMessage();
long suggestionChannelId = CommandOutputChannels.getOutputChannel(message.getGuild().getIdLong(), OutputType.SUGGESTION);
long modLogChannelId = CommandOutputChannels.getOutputChannel(message.getGuild().getIdLong(), OutputType.MOD_LOG);
List<MessageEmbed> embeds = message.getEmbeds();
if (embeds.size() != 1) {
event.replyEmbeds(Util.genericErrorEmbed("Error", "This message contains no embeds, can't be a suggestion"))
.setEphemeral(true).queue(RestAction.getDefaultSuccess(), Util::handleFailure);
return;
}
Guild guild = event.getGuild();
if (guild == null) {
event.replyEmbeds(Util.genericErrorEmbed("Error", "Unable to retrieve guild"))
@ -43,16 +38,9 @@ public class ButtonSuggestionReviewAccept extends DiscordButton {
return;
}
GuildMessageChannel suggestionChannel = guild.getChannelById(GuildMessageChannel.class, suggestionChannelId);
if (suggestionChannel == null) {
event.replyEmbeds(Util.genericErrorEmbed("Error", "This server does not have a valid suggestion channel"))
.setEphemeral(true).queue(RestAction.getDefaultSuccess(), Util::handleFailure);
return;
}
GuildMessageChannel modLogChannel = guild.getChannelById(GuildMessageChannel.class, modLogChannelId);
if (modLogChannel == null) {
event.replyEmbeds(Util.genericErrorEmbed("Error", "This server does not have a valid suggestion channel"))
List<MessageEmbed> embeds = message.getEmbeds();
if (embeds.size() != 1) {
event.replyEmbeds(Util.genericErrorEmbed("Error", "This message contains no embeds, can't be a suggestion"))
.setEphemeral(true).queue(RestAction.getDefaultSuccess(), Util::handleFailure);
return;
}
@ -65,21 +53,86 @@ public class ButtonSuggestionReviewAccept extends DiscordButton {
return;
}
GuildChannel suggestionGuildChannel = CommandOutputChannels.getOutputChannel(message.getGuild(), OutputType.SUGGESTION);
GuildChannel modLogGuildChannel = CommandOutputChannels.getOutputChannel(message.getGuild(), OutputType.MOD_LOG);
TextChannel modLogChannel = validModLogChannel(event, modLogGuildChannel);
if (modLogChannel == null)
return;
if (suggestionGuildChannel == null) {
event.replyEmbeds(Util.genericErrorEmbed("Error", "This server does not have a valid suggestion channel."))
.setEphemeral(true).queue(RestAction.getDefaultSuccess(), Util::handleFailure);
return;
}
String mentionMember = reviewMessage.getDescription();
if (mentionMember == null) {
event.replyEmbeds(Util.genericErrorEmbed("Error", "This message contains no description, can't be a suggestion"))
.setEphemeral(true).queue(RestAction.getDefaultSuccess(), Util::handleFailure);
return;
}
MessageEmbed suggestionMessage = new EmbedBuilder(reviewMessage)
.clearFields()
.setColor(Color.GRAY)
.setTitle(fields.get(0).getName())
.setDescription(fields.get(0).getValue())
.addField("Suggestion by", mentionMember, false)
.build();
if (suggestionGuildChannel instanceof ForumChannel forumChannel) {
sendSuggestionInForum(forumChannel, modLogChannel, fields.get(0), suggestionMessage, mentionMember, event);
} else if (suggestionGuildChannel instanceof TextChannel forumChannel) {
sendSuggestionEmbed(forumChannel, modLogChannel, suggestionMessage, event);
} else {
event.replyEmbeds(Util.genericErrorEmbed("Error", suggestionGuildChannel.getType().name() + " is not a valid suggestion channel"))
.setEphemeral(true).queue(RestAction.getDefaultSuccess(), Util::handleFailure);
}
}
private TextChannel validModLogChannel(ButtonInteractionEvent event, GuildChannel guildChannel) {
if (guildChannel == null) {
event.replyEmbeds(Util.genericErrorEmbed("Error", "This server does not have a valid mod log channel"))
.setEphemeral(true).queue(RestAction.getDefaultSuccess(), Util::handleFailure);
return null;
}
if (!(guildChannel instanceof TextChannel channel)) {
event.replyEmbeds(Util.genericErrorEmbed("Error", "A mod log channel can't be of type: " + guildChannel.getType().name()))
.setEphemeral(true).queue(RestAction.getDefaultSuccess(), Util::handleFailure);
return null;
}
return channel;
}
public void sendSuggestionEmbed(TextChannel suggestionChannel, TextChannel modLog, MessageEmbed suggestionMessage, ButtonInteractionEvent event) {
suggestionChannel.sendMessageEmbeds(suggestionMessage).queue(success -> {
message.delete().queue(RestAction.getDefaultSuccess(), Util::handleFailure);
event.getMessage().delete().queue(RestAction.getDefaultSuccess(), Util::handleFailure);
event.replyEmbeds(Util.genericSuccessEmbed("Success", "The suggestion was accepted and posted in the suggestion channel")).setEphemeral(true).queue();
modLogChannel.sendMessageEmbeds(new EmbedBuilder(suggestionMessage).addField("Accepted", event.getUser().getAsMention(), false).setColor(Color.GREEN).build())
.queue(RestAction.getDefaultSuccess(), Util::handleFailure);
sendModLog(modLog, suggestionMessage, event);
}, failure -> event.replyEmbeds(Util.genericErrorEmbed("Error", "Unable to send suggestion to the suggestion channel"))
.setEphemeral(true).queue(RestAction.getDefaultSuccess(), Util::handleFailure));
}
public void sendSuggestionInForum(ForumChannel forumChannel, TextChannel modLog, MessageEmbed.Field field, MessageEmbed suggestionMessage, String mentionMember, ButtonInteractionEvent event) {
MessageCreateData messageCreateData = new MessageCreateBuilder().addContent("**Suggestion by: " + mentionMember + "**\n\n" + field.getValue() + "\u200B").build();
forumChannel.createForumPost(field.getName(), messageCreateData).queue(success -> {
event.getMessage().delete().queue(RestAction.getDefaultSuccess(), Util::handleFailure);
event.replyEmbeds(Util.genericSuccessEmbed("Success", "The suggestion was accepted and posted in the suggestion channel")).setEphemeral(true).queue();
sendModLog(modLog, suggestionMessage, event);
success.getMessage().addReaction(Emoji.fromUnicode("\uD83D\uDC4D")).queue(RestAction.getDefaultSuccess(), Util::handleFailure);
success.getMessage().addReaction(Emoji.fromUnicode("\uD83D\uDC4E")).queue(RestAction.getDefaultSuccess(), Util::handleFailure);
}, failure -> event.replyEmbeds(Util.genericErrorEmbed("Error", "Unable to send suggestion to the suggestion channel"))
.setEphemeral(true).queue(RestAction.getDefaultSuccess(), Util::handleFailure));
}
public void sendModLog(TextChannel modLog, MessageEmbed suggestionMessage, ButtonInteractionEvent event) {
modLog.sendMessageEmbeds(new EmbedBuilder(suggestionMessage).addField("Accepted", event.getUser().getAsMention(), false).setColor(Color.GREEN).build())
.queue(RestAction.getDefaultSuccess(), Util::handleFailure);
}
@Override
public Button getButton() {
return Button.success(getButtonId(), "Accept Suggestion");

View File

@ -8,9 +8,11 @@ import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.MessageEmbed;
import net.dv8tion.jda.api.entities.channel.middleman.GuildMessageChannel;
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
import net.dv8tion.jda.api.entities.channel.middleman.GuildChannel;
import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent;
import net.dv8tion.jda.api.interactions.components.buttons.Button;
import net.dv8tion.jda.api.requests.RestAction;
import java.awt.*;
import java.util.List;
@ -25,30 +27,30 @@ public class ButtonSuggestionReviewDeny extends DiscordButton {
@Override
public void execute(ButtonInteractionEvent event) {
Message message = event.getMessage();
long channelId = CommandOutputChannels.getOutputChannel(message.getGuild().getIdLong(), OutputType.MOD_LOG);
GuildChannel guildChannel = CommandOutputChannels.getOutputChannel(message.getGuild(), OutputType.MOD_LOG);
TextChannel channel = validModLogChannel(event, guildChannel);
if (channel == null)
return;
List<MessageEmbed> embeds = message.getEmbeds();
if (embeds.size() != 1) {
event.replyEmbeds(Util.genericErrorEmbed("Error", "This message contains no embeds, can't be a suggestion")).setEphemeral(true).queue();
event.replyEmbeds(Util.genericErrorEmbed("Error", "This message contains no embeds, can't be a suggestion"))
.setEphemeral(true).queue(RestAction.getDefaultSuccess(), Util::handleFailure);
return;
}
Guild guild = event.getGuild();
if (guild == null) {
event.replyEmbeds(Util.genericErrorEmbed("Error", "Unable to retrieve guild")).setEphemeral(true).queue();
return;
}
GuildMessageChannel channel = guild.getChannelById(GuildMessageChannel.class, channelId);
if (channel == null) {
event.replyEmbeds(Util.genericErrorEmbed("Error", "This server does not have a valid mod log channel")).setEphemeral(true).queue();
event.replyEmbeds(Util.genericErrorEmbed("Error", "Unable to retrieve guild"))
.setEphemeral(true).queue(RestAction.getDefaultSuccess(), Util::handleFailure);
return;
}
MessageEmbed reviewMessage = embeds.get(0);
List<MessageEmbed.Field> fields = reviewMessage.getFields();
if (fields.size() != 1) {
event.replyEmbeds(Util.genericErrorEmbed("Error", "This message's embed does not contain a field, can't be a suggestion")).setEphemeral(true).queue();
event.replyEmbeds(Util.genericErrorEmbed("Error", "This message's embed does not contain a field, can't be a suggestion"))
.setEphemeral(true).queue(RestAction.getDefaultSuccess(), Util::handleFailure);
return;
}
@ -60,12 +62,29 @@ public class ButtonSuggestionReviewDeny extends DiscordButton {
.build();
channel.sendMessageEmbeds(suggestionMessage).queue(success -> {
message.delete().queue();
event.replyEmbeds(Util.genericSuccessEmbed("Success", "The suggestion was denied and logged")).setEphemeral(true).queue();
event.replyEmbeds(Util.genericSuccessEmbed("Success", "The suggestion was denied and logged"))
.setEphemeral(true).queue(RestAction.getDefaultSuccess(), Util::handleFailure);
}, failure -> {
event.replyEmbeds(Util.genericErrorEmbed("Error", "Unable to send suggestion to the suggestion channel")).setEphemeral(true).queue();
event.replyEmbeds(Util.genericErrorEmbed("Error", "Unable to send suggestion to the suggestion channel"))
.setEphemeral(true).queue(RestAction.getDefaultSuccess(), Util::handleFailure);
});
}
private TextChannel validModLogChannel(ButtonInteractionEvent event, GuildChannel guildChannel) {
if (guildChannel == null) {
event.replyEmbeds(Util.genericErrorEmbed("Error", "This server does not have a valid mod log channel"))
.setEphemeral(true).queue(RestAction.getDefaultSuccess(), Util::handleFailure);
return null;
}
if (!(guildChannel instanceof TextChannel channel)) {
event.replyEmbeds(Util.genericErrorEmbed("Error", "A mod log channel can't be of type: " + guildChannel.getType().name()))
.setEphemeral(true).queue(RestAction.getDefaultSuccess(), Util::handleFailure);
return null;
}
return channel;
}
@Override
public Button getButton() {
return Button.danger(getButtonId(), "Deny Suggestion");

View File

@ -3,6 +3,7 @@ package com.alttd.commandManager;
import com.alttd.commandManager.commands.AddCommand.CommandManage;
import com.alttd.commandManager.commands.*;
import com.alttd.commandManager.commands.PollCommand.CommandPoll;
import com.alttd.contextMenuManager.ContextMenuManager;
import com.alttd.database.Database;
import com.alttd.modalManager.ModalManager;
import com.alttd.util.Logger;
@ -29,12 +30,13 @@ public class CommandManager extends ListenerAdapter {
private final List<DiscordCommand> commands;
private final HashMap<String, List<ScopeInfo>> commandList = new HashMap<>();
public CommandManager(JDA jda, ModalManager modalManager) {
public CommandManager(JDA jda, ModalManager modalManager, ContextMenuManager contextMenuManager) {
commandList.put("manage", new ArrayList<>(List.of(new ScopeInfo(CommandScope.GLOBAL, 0))));
loadCommands();
Logger.info("Loading commands...");
CommandSetToggleableRoles commandSetToggleableRoles = new CommandSetToggleableRoles(jda, this);
commands = List.of(
new CommandManage(jda, this),
new CommandManage(jda, this, contextMenuManager),
new CommandHelp(jda, this),
new CommandPoll(jda, this),
new CommandSuggestion(jda, modalManager, this),
@ -43,8 +45,10 @@ public class CommandManager extends ListenerAdapter {
new CommandEvidence(jda, modalManager, this),
new CommandFlag(jda, this),
new CommandHistory(jda, this),
new CommandSeen(jda, this)
);
new CommandSeen(jda, this),
commandSetToggleableRoles,
new CommandToggleRole(commandSetToggleableRoles, jda, this),
new CommandRemindMe(jda, this, modalManager));
}
@Override

View File

@ -4,6 +4,7 @@ import com.alttd.commandManager.CommandManager;
import com.alttd.commandManager.DiscordCommand;
import com.alttd.commandManager.SubCommand;
import com.alttd.commandManager.SubOption;
import com.alttd.contextMenuManager.ContextMenuManager;
import com.alttd.util.Logger;
import com.alttd.util.Util;
import net.dv8tion.jda.api.JDA;
@ -23,7 +24,7 @@ public class CommandManage extends DiscordCommand {
private final HashMap<String, SubOption> subOptionsMap = new HashMap<>();
private final CommandData commandData;
public CommandManage(JDA jda, CommandManager commandManager) {
public CommandManage(JDA jda, CommandManager commandManager, ContextMenuManager contextMenuManager) {
commandData = Commands.slash(getName(), "Enable commands and assign permissions")
.addSubcommands(
new SubcommandData("enable", "Enable a command in a channel")
@ -34,8 +35,8 @@ public class CommandManage extends DiscordCommand {
.setDefaultPermissions(DefaultMemberPermissions.enabledFor(Permission.ADMINISTRATOR))
.setGuildOnly(true);
Util.registerSubOptions(subOptionsMap,
new SubCommandEnable(commandManager, null, this),
new SubCommandEnable(commandManager, null, this)
new SubCommandEnable(commandManager, contextMenuManager, null, this),
new SubCommandDisable(commandManager, null, this)
);
Util.registerCommand(commandManager, jda, commandData, getName());
}

View File

@ -1,20 +1,19 @@
package com.alttd.commandManager.commands.AddCommand;
import com.alttd.commandManager.*;
import com.alttd.contextMenuManager.ContextMenuManager;
import com.alttd.contextMenuManager.DiscordContextMenu;
import com.alttd.database.Database;
import com.alttd.templates.Parser;
import com.alttd.templates.Template;
import com.alttd.util.Logger;
import com.alttd.util.Util;
import com.google.protobuf.GeneratedMessageV3;
import com.mysql.cj.log.Log;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.events.interaction.command.CommandAutoCompleteInteractionEvent;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import net.dv8tion.jda.api.interactions.commands.OptionMapping;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
@ -23,10 +22,12 @@ import java.util.stream.Collectors;
public class SubCommandEnable extends SubCommand {
private final CommandManager commandManager;
private final ContextMenuManager contextMenuManager;
protected SubCommandEnable(CommandManager commandManager, SubCommandGroup parentGroup, DiscordCommand parent) {
protected SubCommandEnable(CommandManager commandManager, ContextMenuManager contextMenuManager, SubCommandGroup parentGroup, DiscordCommand parent) {
super(parentGroup, parent);
this.commandManager = commandManager;
this.contextMenuManager = contextMenuManager;
}
@Override
@ -50,11 +51,22 @@ public class SubCommandEnable extends SubCommand {
String commandName = option.getAsString();
DiscordCommand command = commandManager.getCommand(commandName);
if (command == null) {
event.replyEmbeds(Util.genericErrorEmbed("Error", "Unable to find a command called [" + commandName + "].")).setEphemeral(true).queue();
if (command != null) {
tryEnableCommand(command, guild, commandName, event);
return;
}
DiscordContextMenu contextMenu = contextMenuManager.getContext(commandName);
if (contextMenu != null) {
tryEnableContextMenu(contextMenu, guild, commandName, event);
//todo stuff
return;
}
event.replyEmbeds(Util.genericErrorEmbed("Error", "Unable to find a command called [" + commandName + "].")).setEphemeral(true).queue();
}
private void tryEnableCommand(DiscordCommand command, Guild guild, String commandName, SlashCommandInteractionEvent event) {
if (enableCommand(command, guild.getIdLong())) {
Util.registerCommand(guild, command.getCommandData(), command.getName());
event.replyEmbeds(Util.genericSuccessEmbed("Enabled command",
@ -71,6 +83,23 @@ public class SubCommandEnable extends SubCommand {
}
}
private void tryEnableContextMenu(DiscordContextMenu contextMenu, Guild guild, String commandName, SlashCommandInteractionEvent event) {
if (enableContextMenu(contextMenu, guild.getIdLong())) {
Util.registerCommand(guild, contextMenu.getUserContextInteraction(), contextMenu.getContextMenuId());
event.replyEmbeds(Util.genericSuccessEmbed("Enabled command",
Parser.parse("Successfully enabled <command> in <guild>!",
Template.of("command", commandName.toLowerCase()),
Template.of("guild", guild.getName())
))).setEphemeral(true).queue();
} else {
event.replyEmbeds(Util.genericErrorEmbed("Failed to enable command",
Parser.parse("Unable to enable <command> in <guild>, is it already enabled?",
Template.of("command", commandName.toLowerCase()),
Template.of("guild", guild.getName())
))).setEphemeral(true).queue();
}
}
private boolean enableCommand(DiscordCommand command, long guildId) {
if (!commandManager.enableCommand(command.getName(), new ScopeInfo(CommandScope.GUILD, guildId)))
return false;
@ -100,6 +129,35 @@ public class SubCommandEnable extends SubCommand {
return true;
}
private boolean enableContextMenu(DiscordContextMenu contextMenu, long guildId) {
if (!commandManager.enableCommand(contextMenu.getContextMenuId(), new ScopeInfo(CommandScope.GUILD, guildId)))
return false;
String sql = "INSERT INTO commands (command_name, scope, location_id) VALUES(?, ?, ?)";
PreparedStatement statement = null;
try {
statement = Database.getDatabase().getConnection().prepareStatement(sql);
statement.setString(1, contextMenu.getContextMenuId());
statement.setString(2, "GUILD");
statement.setLong(3, guildId);
if (statement.executeUpdate() == 0) {
Logger.warning("Unable to enable command: % for guild: %", contextMenu.getContextMenuId(), String.valueOf(guildId));
return false;
}
} catch (SQLException exception) {
Logger.sql(exception);
return false;
} finally {
try {
if (statement != null)
statement.close();
} catch (SQLException exception) {
Logger.sql(exception);
}
}
return true;
}
@Override
public void suggest(CommandAutoCompleteInteractionEvent event) {
OptionMapping option = event.getOption("command");
@ -110,13 +168,21 @@ public class SubCommandEnable extends SubCommand {
}
String commandName = option.getAsString().toLowerCase();
ScopeInfo scopeInfo = new ScopeInfo(CommandScope.GLOBAL, event.getGuild().getIdLong());
event.replyChoiceStrings(commandManager.getCommands().stream()
.map(DiscordCommand::getName)
.filter(name -> name.toLowerCase().startsWith(commandName))
.filter(name -> !commandManager.getActiveLocations(name).contains(scopeInfo))
.limit(25)
.collect(Collectors.toList()))
.queue();
List<String> collect = commandManager.getCommands().stream()
.map(DiscordCommand::getName)
.filter(name -> name.toLowerCase().startsWith(commandName))
.filter(name -> !commandManager.getActiveLocations(name).contains(scopeInfo))
.limit(25)
.collect(Collectors.toList());
collect.addAll(contextMenuManager.getContexts().stream()
.map(DiscordContextMenu::getContextMenuId)
.filter(name -> name.toLowerCase().startsWith(commandName))
.filter(name -> !commandManager.getActiveLocations(name).contains(scopeInfo))
.limit(25)
.collect(Collectors.toList()));
event.replyChoiceStrings(collect).queue();
}
@Override

View File

@ -1,4 +1,186 @@
package com.alttd.commandManager.commands;
public class CommandRemindMe {
import com.alttd.commandManager.CommandManager;
import com.alttd.commandManager.DiscordCommand;
import com.alttd.modalManager.ModalManager;
import com.alttd.modalManager.modals.ModalRemindMe;
import com.alttd.util.Util;
import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
import net.dv8tion.jda.api.entities.channel.unions.GuildChannelUnion;
import net.dv8tion.jda.api.events.interaction.command.CommandAutoCompleteInteractionEvent;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import net.dv8tion.jda.api.interactions.AutoCompleteQuery;
import net.dv8tion.jda.api.interactions.commands.DefaultMemberPermissions;
import net.dv8tion.jda.api.interactions.commands.OptionMapping;
import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.build.CommandData;
import net.dv8tion.jda.api.interactions.commands.build.Commands;
import net.dv8tion.jda.api.interactions.components.Modal;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;
public class CommandRemindMe extends DiscordCommand {
private final CommandData commandData;
private final ModalManager modalManager;
public CommandRemindMe(JDA jda, CommandManager commandManager, ModalManager modalManager) {
this.modalManager = modalManager;
commandData = Commands.slash(getName(), "Create a reminder")
.addOption(OptionType.CHANNEL, "channel", "The channel to send the reminder in", true)
.addOption(OptionType.STRING, "fromnow", "How long from now the reminder should send", true, true)
.setDefaultPermissions(DefaultMemberPermissions.ENABLED)
.setGuildOnly(true);
Util.registerCommand(commandManager, jda, commandData, getName());
}
@Override
public String getName() {
return "remindme";
}
@Override
public void execute(SlashCommandInteractionEvent event) {
TextChannel channel = getValidChannel(
event.getInteraction().getOption("channel", OptionMapping::getAsChannel), event);
if (channel == null)
return;
Long fromNow = getFromNow(event.getInteraction().getOption("fromnow", OptionMapping::getAsString), event);
if (fromNow == null)
return;
Modal modal = modalManager.getModalFor("remindme");
if (modal == null) {
event.replyEmbeds(Util.genericErrorEmbed("Error", "Unable to retrieve remind me modal"))
.setEphemeral(true).queue();
return;
}
ModalRemindMe.putData(event.getUser().getIdLong(), channel, fromNow);
event.replyModal(modal).queue();
}
private TextChannel getValidChannel(GuildChannelUnion channel, SlashCommandInteractionEvent event) {
if (channel == null) {
event.replyEmbeds(Util.genericErrorEmbed("Error", "Couldn't find channel"))
.setEphemeral(true).queue();
return null;
}
if (!(channel instanceof TextChannel textChannel)) {
event.replyEmbeds(Util.genericErrorEmbed("Error", "Not a valid TextChannel"))
.setEphemeral(true).queue();
return null;
}
if (!textChannel.canTalk()) {
event.replyEmbeds(Util.genericErrorEmbed("Error", "I can't talk in this channel"))
.setEphemeral(true).queue();
return null;
}
return textChannel;
}
private Long getFromNow(String fromNow, SlashCommandInteractionEvent event) {
if (fromNow == null) {
event.replyEmbeds(Util.genericErrorEmbed("Error", "Couldn't find from now option"))
.setEphemeral(true).queue();
return null;
}
if (!fromNow.matches("[1-9][0-9]*[hdmy]")) {
return fromNowTimestamp(fromNow, event);
}
int i;
try {
i = Integer.parseInt(fromNow.substring(0, fromNow.length() - 1));
} catch (NumberFormatException e) {
event.replyEmbeds(Util.genericErrorEmbed("Error", "Invalid number"))
.setEphemeral(true).queue();
return null;
}
switch (fromNow.substring(fromNow.length() - 1)) {
case "h" -> {
return TimeUnit.HOURS.toMillis(i) + new Date().getTime();
}
case "d" -> {
return TimeUnit.DAYS.toMillis(i) + new Date().getTime();
}
case "m" -> {
Calendar instance = Calendar.getInstance();
instance.setTime(new Date());
instance.add(Calendar.MONTH, i);
return instance.getTimeInMillis();
}
case "y" -> {
Calendar instance = Calendar.getInstance();
instance.setTime(new Date());
instance.add(Calendar.YEAR, i);
return instance.getTimeInMillis();
}
default -> {
event.replyEmbeds(Util.genericErrorEmbed("Error", "Invalid format? This shouldn't be possible..."))
.setEphemeral(true).queue();
return null;
}
}
}
private Long fromNowTimestamp(String fromNow, SlashCommandInteractionEvent event) {
if (!fromNow.matches("t:[1-9][0-9]*")) {
event.replyEmbeds(Util.genericErrorEmbed("Error", "Invalid from now format ex: `1d`"))
.setEphemeral(true).queue();
return null;
}
long l;
try {
l = Long.parseLong(fromNow.substring(2));
} catch (NumberFormatException e) {
event.replyEmbeds(Util.genericErrorEmbed("Error", "Invalid number"))
.setEphemeral(true).queue();
return null;
}
return l;
}
@Override
public void suggest(CommandAutoCompleteInteractionEvent event) {
AutoCompleteQuery focusedOption = event.getFocusedOption();
if (!focusedOption.getName().equals("fromnow")) {
event.replyChoices(Collections.emptyList()).queue();
return;
}
String value = focusedOption.getValue();
if (value.isBlank()) {
event.replyChoiceStrings(List.of("1h", "1d", "1m", "1y", "t:" + new Date().getTime())).queue();
return;
}
if (value.matches("[0-9]+"))
event.replyChoiceStrings(List.of(value + "h", value + "d", value + "m", value + "y")).queue();
else if (value.startsWith("t:"))
event.replyChoiceStrings(List.of(value)).queue();
else
event.replyChoices(Collections.emptyList()).queue();
}
@Override
public String getHelpMessage() {
return null;
}
@Override
public CommandData getCommandData() {
return commandData;
}
}

View File

@ -8,6 +8,7 @@ import com.alttd.util.Util;
import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.channel.ChannelType;
import net.dv8tion.jda.api.entities.channel.unions.GuildChannelUnion;
import net.dv8tion.jda.api.events.interaction.command.CommandAutoCompleteInteractionEvent;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
@ -71,10 +72,11 @@ public class CommandSetOutputChannel extends DiscordCommand {
return;
}
GuildChannelUnion channel = option.getAsChannel();
switch (channel.getType()) {
case TEXT, NEWS, GUILD_NEWS_THREAD, GUILD_PUBLIC_THREAD, GUILD_PRIVATE_THREAD -> {
boolean success = CommandOutputChannels.setOutputChannel(guild.getIdLong(), outputType, channel.getIdLong());
ChannelType channelType = option.getChannelType();
switch (channelType) {
case TEXT, NEWS, GUILD_NEWS_THREAD, GUILD_PUBLIC_THREAD, GUILD_PRIVATE_THREAD, FORUM -> {
GuildChannelUnion channel = option.getAsChannel();
boolean success = CommandOutputChannels.setOutputChannel(guild.getIdLong(), outputType, channel.getIdLong(), channelType);
if (success)
event.replyEmbeds(Util.genericSuccessEmbed("Success", "Set channel " + channel.getAsMention() + " as the output channel for " + outputType.name() + "."))
.setEphemeral(true).queue(RestAction.getDefaultSuccess(), Util::handleFailure);
@ -82,7 +84,7 @@ public class CommandSetOutputChannel extends DiscordCommand {
event.replyEmbeds(Util.genericErrorEmbed("Error", "Unable to store the new channel output in the database"))
.setEphemeral(true).queue(RestAction.getDefaultSuccess(), Util::handleFailure);
}
default -> event.replyEmbeds(Util.genericErrorEmbed("Error", "The channel type " + channel.getType().name() + " is not a valid output channel type"))
default -> event.replyEmbeds(Util.genericErrorEmbed("Error", "The channel type " + channelType.name() + " is not a valid output channel type"))
.setEphemeral(true).queue(RestAction.getDefaultSuccess(), Util::handleFailure);
}

View File

@ -0,0 +1,147 @@
package com.alttd.commandManager.commands;
import com.alttd.commandManager.CommandManager;
import com.alttd.commandManager.DiscordCommand;
import com.alttd.database.queries.QueriesToggleableRoles;
import com.alttd.util.Util;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.MessageEmbed;
import net.dv8tion.jda.api.entities.Role;
import net.dv8tion.jda.api.events.interaction.command.CommandAutoCompleteInteractionEvent;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import net.dv8tion.jda.api.interactions.commands.DefaultMemberPermissions;
import net.dv8tion.jda.api.interactions.commands.OptionMapping;
import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.build.CommandData;
import net.dv8tion.jda.api.interactions.commands.build.Commands;
import net.dv8tion.jda.api.requests.RestAction;
import java.awt.*;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.stream.Collectors;
public class CommandSetToggleableRoles extends DiscordCommand {
private final HashMap<Long, HashSet<Long>> guildToRolesMap;
private final CommandData commandData;
public CommandSetToggleableRoles(JDA jda, CommandManager commandManager) {
guildToRolesMap = QueriesToggleableRoles.getToggleableRoles();
commandData = Commands.slash(getName(), "Set which roles can be toggled")
.addOption(OptionType.ROLE, "role", "The role you want to toggle on/off", false)
.setDefaultPermissions(DefaultMemberPermissions.DISABLED);
Util.registerCommand(commandManager, jda, commandData, getName());
}
@Override
public String getName() {
return "settoggleableroles";
}
@Override
public void execute(SlashCommandInteractionEvent event) {
Guild guild = event.getGuild();
if (guild == null) {
event.replyEmbeds(Util.genericErrorEmbed("Error", "This command has to be ran in a guild"))
.setEphemeral(true).queue(RestAction.getDefaultSuccess(), Util::handleFailure);
return;
}
List<OptionMapping> options = event.getInteraction().getOptions();
if (options.size() == 0) {
String toggleableRoles = getToggleableRoles(guild);
MessageEmbed messageEmbed = new EmbedBuilder()
.setTitle("Active roles")
.setColor(Color.GREEN)
.setDescription(toggleableRoles)
.build();
event.replyEmbeds(messageEmbed).setEphemeral(true).queue(RestAction.getDefaultSuccess(), Util::handleFailure);
return;
}
OptionMapping optionMapping = options.get(0);
Role role = optionMapping.getAsRole();
if (containsRole(role)) {
if (!QueriesToggleableRoles.removeRoleToggleable(role)) {
event.replyEmbeds(Util.genericErrorEmbed("Error", "Unable to remove role from the database"))
.setEphemeral(true).queue(RestAction.getDefaultSuccess(), Util::handleFailure);
return;
}
removeRole(role);
event.replyEmbeds(Util.genericSuccessEmbed("Success", "Removed " + role.getAsMention() + " from the toggleable roles"))
.setEphemeral(true).queue(RestAction.getDefaultSuccess(), Util::handleFailure);
} else {
if (role.hasPermission(Permission.ADMINISTRATOR) ||
role.hasPermission(Permission.MANAGE_ROLES) ||
role.hasPermission(Permission.MANAGE_CHANNEL) ||
role.hasPermission(Permission.MANAGE_THREADS) ||
role.hasPermission(Permission.MANAGE_WEBHOOKS) ||
role.hasPermission(Permission.MANAGE_SERVER) ||
role.hasPermission(Permission.MANAGE_PERMISSIONS) ||
role.hasPermission(Permission.MESSAGE_MANAGE) ||
role.hasPermission(Permission.MODERATE_MEMBERS)) {
event.replyEmbeds(Util.genericErrorEmbed("Error", "For safety reason this bot can not add roles which have a manage or moderator permission"))
.setEphemeral(true).queue(RestAction.getDefaultSuccess(), Util::handleFailure);
return;
}
if (!QueriesToggleableRoles.addRoleToggleable(role)) {
event.replyEmbeds(Util.genericErrorEmbed("Error", "Unable to store role in the database"))
.setEphemeral(true).queue(RestAction.getDefaultSuccess(), Util::handleFailure);
return;
}
addRole(role);
event.replyEmbeds(Util.genericSuccessEmbed("Success", "Added " + role.getAsMention() + " to the toggleable roles"))
.setEphemeral(true).queue(RestAction.getDefaultSuccess(), Util::handleFailure);
}
}
private void addRole(Role role) {
long guild = role.getGuild().getIdLong();
HashSet<Long> set = guildToRolesMap.getOrDefault(guild, new HashSet<>());
set.add(role.getIdLong());
guildToRolesMap.put(guild, set);
}
private void removeRole(Role role) {
long guild = role.getGuild().getIdLong();
HashSet<Long> set = guildToRolesMap.getOrDefault(guild, new HashSet<>());
if (set.isEmpty())
return;
set.remove(role.getIdLong());
guildToRolesMap.put(guild, set);
}
public boolean containsRole(Role role) {
return guildToRolesMap.getOrDefault(role.getGuild().getIdLong(), new HashSet<>()).contains(role.getIdLong());
}
public String getToggleableRoles(Guild guild) {
HashSet<Long> roleIds = guildToRolesMap.get(guild.getIdLong());
return guild.getRoles().stream()
.filter(role -> roleIds.contains(role.getIdLong()))
.map(Role::getAsMention)
.collect(Collectors.joining("\n"));
}
@Override
public void suggest(CommandAutoCompleteInteractionEvent event) {
event.replyChoices(Collections.emptyList()).queue();
}
@Override
public String getHelpMessage() {
return null;
}
@Override
public CommandData getCommandData() {
return commandData;
}
}

View File

@ -0,0 +1,108 @@
package com.alttd.commandManager.commands;
import com.alttd.commandManager.CommandManager;
import com.alttd.commandManager.DiscordCommand;
import com.alttd.util.Util;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.MessageEmbed;
import net.dv8tion.jda.api.entities.Role;
import net.dv8tion.jda.api.events.interaction.command.CommandAutoCompleteInteractionEvent;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import net.dv8tion.jda.api.interactions.commands.DefaultMemberPermissions;
import net.dv8tion.jda.api.interactions.commands.OptionMapping;
import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.build.CommandData;
import net.dv8tion.jda.api.interactions.commands.build.Commands;
import net.dv8tion.jda.api.requests.RestAction;
import java.awt.*;
import java.util.Collections;
import java.util.List;
public class CommandToggleRole extends DiscordCommand {
private final CommandSetToggleableRoles commandSetToggleableRoles;
private final CommandData commandData;
public CommandToggleRole(CommandSetToggleableRoles commandSetToggleableRoles, JDA jda, CommandManager commandManager) {
this.commandSetToggleableRoles = commandSetToggleableRoles;
commandData = Commands.slash(getName(), "Toggle a role")
.addOption(OptionType.ROLE, "role", "The role you want to toggle on/off (run the command without this option to see all available roles)", false)
.setDefaultPermissions(DefaultMemberPermissions.ENABLED);
Util.registerCommand(commandManager, jda, commandData, getName());
}
@Override
public String getName() {
return "togglerole";
}
@Override
public void execute(SlashCommandInteractionEvent event) {
Guild guild = event.getGuild();
if (guild == null) {
event.replyEmbeds(Util.genericErrorEmbed("Error", "This command has to be ran in a guild"))
.setEphemeral(true).queue(RestAction.getDefaultSuccess(), Util::handleFailure);
return;
}
List<OptionMapping> options = event.getInteraction().getOptions();
if (options.size() == 0) {
String toggleableRoles = commandSetToggleableRoles.getToggleableRoles(guild);
MessageEmbed messageEmbed = new EmbedBuilder()
.setTitle("Toggleable roles")
.setColor(Color.GREEN)
.setDescription(toggleableRoles)
.build();
event.replyEmbeds(messageEmbed).setEphemeral(true).queue(RestAction.getDefaultSuccess(), Util::handleFailure);
return;
}
OptionMapping optionMapping = options.get(0);
Role role = optionMapping.getAsRole();
if (!commandSetToggleableRoles.containsRole(role)) {
event.replyEmbeds(Util.genericErrorEmbed("Error", "This role is not toggleable!"))
.setEphemeral(true).queue(RestAction.getDefaultSuccess(), Util::handleFailure);
return;
}
Member member = event.getMember();
if (member == null) {
event.replyEmbeds(Util.genericErrorEmbed("Error", "This command has to be ran in a guild"))
.setEphemeral(true).queue(RestAction.getDefaultSuccess(), Util::handleFailure);
return;
}
if (member.getRoles().contains(role)) {
guild.removeRoleFromMember(member, role).queue(success ->
event.replyEmbeds(Util.genericSuccessEmbed("Role removed", "You no longer have " + role.getAsMention() + "."))
.setEphemeral(true).queue(RestAction.getDefaultSuccess(), Util::handleFailure),
error -> event.replyEmbeds(Util.genericErrorEmbed("Error", "Unable to manage your roles."))
.setEphemeral(true).queue(RestAction.getDefaultSuccess(), Util::handleFailure));
} else {
guild.addRoleToMember(member, role).queue(success ->
event.replyEmbeds(Util.genericSuccessEmbed("Role add", "You now have " + role.getAsMention() + "."))
.setEphemeral(true).queue(RestAction.getDefaultSuccess(), Util::handleFailure),
error -> event.replyEmbeds(Util.genericErrorEmbed("Error", "Unable to manage your roles."))
.setEphemeral(true).queue(RestAction.getDefaultSuccess(), Util::handleFailure));
}
}
@Override
public void suggest(CommandAutoCompleteInteractionEvent event) {
event.replyChoices(Collections.emptyList()).queue();
}
@Override
public String getHelpMessage() {
return null;
}
@Override
public CommandData getCommandData() {
return commandData;
}
}

View File

@ -22,14 +22,14 @@ import java.util.Map;
import java.util.regex.Pattern;
@SuppressWarnings({"unused", "SameParameterValue"})
abstract class AbstractConfig {
public abstract class AbstractConfig {
private static final Pattern PATH_PATTERN = Pattern.compile("\\.");
private static final String HEADER = "";
private YamlConfigurationLoader configLoader;
private ConfigurationNode config;
AbstractConfig(String filename) {
protected AbstractConfig(String filename) {
init(new File(new File(AltitudeBot.getInstance().getDataFolder()).getParentFile(), filename), filename);
}
@ -60,7 +60,7 @@ abstract class AbstractConfig {
}
}
void readConfig(Class<?> clazz, Object instance) {
protected void readConfig(Class<?> clazz, Object instance) {
for (Method method : clazz.getDeclaredMethods()) {
if (Modifier.isPrivate(method.getModifiers())) {
if (method.getParameterTypes().length == 0 && method.getReturnType() == Void.TYPE) {
@ -80,7 +80,7 @@ abstract class AbstractConfig {
save();
}
private void save() {
protected void save() {
try {
configLoader.save(config);
} catch (IOException ex) {
@ -101,6 +101,17 @@ abstract class AbstractConfig {
}
}
protected void update(String path, Object def) {
if(config.node(splitPath(path)).virtual()) {
set(path, def);
return;
}
try {
config.node(splitPath(path)).set(def);
} catch (SerializationException e) {
}
}
protected void setString(String path, String def) {
try {
if(config.node(splitPath(path)).virtual())

View File

@ -0,0 +1,76 @@
package com.alttd.contextMenuManager;
import com.alttd.contextMenuManager.contextMenus.ContextMenuRespondSuggestion;
import com.alttd.modalManager.ModalManager;
import com.alttd.util.Util;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.events.interaction.command.MessageContextInteractionEvent;
import net.dv8tion.jda.api.events.interaction.command.UserContextInteractionEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import net.dv8tion.jda.api.requests.RestAction;
import javax.annotation.Nonnull;
import java.awt.*;
import java.util.List;
import java.util.Optional;
public class ContextMenuManager extends ListenerAdapter {
private final List<DiscordContextMenu> contextMenus;
public ContextMenuManager(ModalManager modalManager) {
contextMenus = List.of(
new ContextMenuRespondSuggestion(modalManager)
);
}
public DiscordContextMenu getContext(String name) {
for (DiscordContextMenu contextMenu : contextMenus) {
if (contextMenu.getContextMenuId().equalsIgnoreCase(name))
return contextMenu;
}
return null;
}
public List<DiscordContextMenu> getContexts() {
return contextMenus;
}
@Override
public void onUserContextInteraction(@Nonnull UserContextInteractionEvent event) {
String name = event.getInteraction().getName();
Optional<DiscordContextMenu> first = contextMenus.stream()
.filter(discordModal -> discordModal.getContextMenuId().equalsIgnoreCase(name))
.findFirst();
if (first.isEmpty()) {
event.replyEmbeds(new EmbedBuilder()
.setTitle("Invalid command")
.setDescription("Unable to process user context interaction with id: [" + name + "].")
.setColor(Color.RED)
.build())
.setEphemeral(true)
.queue(RestAction.getDefaultSuccess(), Util::handleFailure);
return;
}
first.get().execute(event);
}
@Override
public void onMessageContextInteraction(@Nonnull MessageContextInteractionEvent event) {
String name = event.getInteraction().getName();
Optional<DiscordContextMenu> first = contextMenus.stream()
.filter(discordModal -> discordModal.getContextMenuId().equalsIgnoreCase(name))
.findFirst();
if (first.isEmpty()) {
event.replyEmbeds(new EmbedBuilder()
.setTitle("Invalid command")
.setDescription("Unable to process user context interaction with id: [" + name + "].")
.setColor(Color.RED)
.build())
.setEphemeral(true)
.queue(RestAction.getDefaultSuccess(), Util::handleFailure);
return;
}
first.get().execute(event);
}
}

View File

@ -0,0 +1,17 @@
package com.alttd.contextMenuManager;
import net.dv8tion.jda.api.events.interaction.command.MessageContextInteractionEvent;
import net.dv8tion.jda.api.events.interaction.command.UserContextInteractionEvent;
import net.dv8tion.jda.api.interactions.commands.build.CommandData;
public abstract class DiscordContextMenu {
public abstract String getContextMenuId();
public abstract void execute(UserContextInteractionEvent event);
public abstract void execute(MessageContextInteractionEvent event);
public abstract CommandData getUserContextInteraction();
}

View File

@ -0,0 +1,90 @@
package com.alttd.contextMenuManager.contextMenus;
import com.alttd.contextMenuManager.DiscordContextMenu;
import com.alttd.database.queries.commandOutputChannels.CommandOutputChannels;
import com.alttd.database.queries.commandOutputChannels.OutputType;
import com.alttd.modalManager.ModalManager;
import com.alttd.modalManager.modals.ModalReplySuggestion;
import com.alttd.util.Util;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.ChannelType;
import net.dv8tion.jda.api.entities.channel.concrete.ThreadChannel;
import net.dv8tion.jda.api.entities.channel.middleman.GuildChannel;
import net.dv8tion.jda.api.entities.channel.unions.IThreadContainerUnion;
import net.dv8tion.jda.api.entities.channel.unions.MessageChannelUnion;
import net.dv8tion.jda.api.events.interaction.command.MessageContextInteractionEvent;
import net.dv8tion.jda.api.events.interaction.command.UserContextInteractionEvent;
import net.dv8tion.jda.api.interactions.commands.DefaultMemberPermissions;
import net.dv8tion.jda.api.interactions.commands.build.CommandData;
import net.dv8tion.jda.api.interactions.commands.build.Commands;
import net.dv8tion.jda.api.interactions.components.Modal;
import net.dv8tion.jda.api.requests.RestAction;
public class ContextMenuRespondSuggestion extends DiscordContextMenu {
private final ModalManager modalManager;
public ContextMenuRespondSuggestion(ModalManager modalManager) {
this.modalManager = modalManager;
}
@Override
public String getContextMenuId() {
return "Respond To Suggestion";
}
@Override
public void execute(UserContextInteractionEvent event) {
event.getInteraction().replyEmbeds(Util.genericErrorEmbed("Error", "This interaction should have been a message interaction"))
.setEphemeral(true).queue(RestAction.getDefaultSuccess(), Util::handleFailure);
}
@Override
public void execute(MessageContextInteractionEvent event) {
Message message = event.getInteraction().getTarget();
if (!isSuggestion(message)) {
event.replyEmbeds(Util.genericErrorEmbed("Error", "This is not a suggestion"))
.setEphemeral(true).queue(RestAction.getDefaultSuccess(), Util::handleFailure);
return;
}
Modal replySuggestion = modalManager.getModalFor("reply_suggestion");
if (replySuggestion == null) {
event.replyEmbeds(Util.genericErrorEmbed("Error", "Unable to find reply suggestion modal"))
.setEphemeral(true).queue(RestAction.getDefaultSuccess(), Util::handleFailure);
return;
}
ModalReplySuggestion.putMessage(event.getUser().getIdLong(), message); //TODO find a better way to do this
event.replyModal(replySuggestion).queue(RestAction.getDefaultSuccess(), Util::handleFailure);
}
@Override
public CommandData getUserContextInteraction() {
return Commands.message(getContextMenuId())
.setGuildOnly(true)
.setDefaultPermissions(DefaultMemberPermissions.enabledFor(Permission.ADMINISTRATOR));
}
public boolean isSuggestion(Message message) {
GuildChannel channel = CommandOutputChannels.getOutputChannel(message.getGuild(), OutputType.SUGGESTION);
if (channel == null)
return false;
MessageChannelUnion messageChannel = message.getChannel();
if (channel.getType().equals(ChannelType.FORUM)) {
if (messageChannel.getType() != ChannelType.GUILD_PUBLIC_THREAD) {
return false;
}
ThreadChannel threadChannel = messageChannel.asThreadChannel();
IThreadContainerUnion parentChannel = threadChannel.getParentChannel();
if (!parentChannel.getType().equals(ChannelType.FORUM))
return false;
return message.getIdLong() == messageChannel.getIdLong() && message.getAuthor().equals(message.getJDA().getSelfUser());
} else {
return channel.equals(messageChannel);
}
}
}

View File

@ -65,7 +65,7 @@ public class DatabaseTables {
connection.prepareStatement(sql).executeUpdate();
} catch (SQLException e) {
Logger.sql(e);
Logger.severe("Unable to create polls table, shutting down...");
Logger.severe("Unable to create commands table, shutting down...");
}
}
@ -74,13 +74,50 @@ public class DatabaseTables {
"guild BIGINT NOT NULL, " +
"output_type VARCHAR(64) NOT NULL, " +
"channel BIGINT NOT NULL, " +
"channel_type VARCHAR(64) NOT NULL, " +
"PRIMARY KEY (guild, output_type, channel)" +
")";
try {
connection.prepareStatement(sql).executeUpdate();
} catch (SQLException e) {
Logger.sql(e);
Logger.severe("Unable to create polls table, shutting down...");
Logger.severe("Unable to create output channel table, shutting down...");
}
}
private void createToggleableRolesTable() {
String sql = "CREATE TABLE IF NOT EXISTS toggleable_roles(" +
"guild BIGINT NOT NULL, " +
"role BIGINT NOT NULL, " +
"PRIMARY KEY (guild, role)" +
")";
try {
connection.prepareStatement(sql).executeUpdate();
} catch (SQLException e) {
Logger.sql(e);
Logger.severe("Unable to create toggleable roles table, shutting down...");
}
}
private void createReminderTable() {
String sql = "CREATE TABLE IF NOT EXISTS new_reminders(" +
"id INT NOT NULL AUTO_INCREMENT, " +
"title VARCHAR(256) NOT NULL, " +
"description VARCHAR(4096) NOT NULL, " +
"user_id LONG NOT NULL, " +
"guild_id LONG NOT NULL, " +
"channel_id LONG NOT NULL, " +
"message_id LONG NOT NULL, " +
"should_repeat TINYINT(1) NOT NULL, " +
"creation_date LONG NOT NULL, " +
"remind_date LONG NOT NULL, " +
"PRIMARY KEY (id)" +
")";
try {
connection.prepareStatement(sql).executeUpdate();
} catch (SQLException e) {
Logger.sql(e);
Logger.severe("Unable to create reminders table, shutting down...");
}
}

View File

@ -0,0 +1,90 @@
package com.alttd.database.queries.QueriesReminders;
import com.alttd.database.Database;
import com.alttd.util.Logger;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
public class QueriesReminders {
public static int storeReminder(Reminder reminder) {
String sql = "INSERT INTO new_reminders " +
"(title, description, user_id, guild_id, channel_id, message_id, should_repeat, creation_date, remind_date) " +
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)";
try {
PreparedStatement preparedStatement = Database.getDatabase().getConnection().prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
preparedStatement.setString(1, reminder.title());
preparedStatement.setString(2, reminder.description());
preparedStatement.setLong(3, reminder.userId());
preparedStatement.setLong(4, reminder.guildId());
preparedStatement.setLong(5, reminder.channelId());
preparedStatement.setLong(6, 0);
preparedStatement.setInt(7, reminder.shouldRepeat() ? 1 : 0);
preparedStatement.setLong(8, reminder.creationDate());
preparedStatement.setLong(9, reminder.remindDate());
if (preparedStatement.executeUpdate() == 1) {
ResultSet generatedKeys = preparedStatement.getGeneratedKeys();
if (generatedKeys.next()) {
return generatedKeys.getInt(1);
}
}
return -1;
} catch (SQLException e) {
Logger.exception(e);
}
return -1;
}
public static boolean removeReminder(int id) {
String sql = "DELETE FROM new_reminders WHERE id = ?";
try {
PreparedStatement preparedStatement = Database.getDatabase().getConnection().prepareStatement(sql);
preparedStatement.setInt(1, id);
return preparedStatement.executeUpdate() == 1;
} catch (SQLException e) {
Logger.exception(e);
}
return false;
}
public static ArrayList<Reminder> getReminders() {
String sql = "SELECT * FROM new_reminders";
try {
ArrayList<Reminder> reminders = new ArrayList<>();
PreparedStatement preparedStatement = Database.getDatabase().getConnection().prepareStatement(sql);
ResultSet resultSet = preparedStatement.executeQuery();
while (resultSet.next()) {
reminders.add(getReminder(resultSet));
}
return reminders;
} catch (SQLException e) {
Logger.exception(e);
}
return null;
}
private static Reminder getReminder(ResultSet resultSet) throws SQLException {
int id = resultSet.getInt("id");
String title = resultSet.getString("title");
String desc = resultSet.getString("description");
long userId = resultSet.getLong("user_id");
long guildId = resultSet.getLong("guild_id");
long channelId = resultSet.getLong("channel_id");
long messageId = resultSet.getLong("message_id");
boolean shouldRepeat = resultSet.getInt("should_repeat") == 1;
long creationDate = resultSet.getLong("creation_date");
long remindDate = resultSet.getLong("remind_date");
return new Reminder(id, title, desc, userId, guildId, channelId, messageId, shouldRepeat, creationDate, remindDate);
}
}

View File

@ -0,0 +1,33 @@
package com.alttd.database.queries.QueriesReminders;
import com.alttd.util.Logger;
import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
public record Reminder (int id, String title, String description, long userId, long guildId, long channelId,
long messageId, boolean shouldRepeat, long creationDate, long remindDate) {
public TextChannel getChannel(JDA jda) {
Guild guildById = getGuild(jda);
if (guildById == null)
return null;
TextChannel textChannelById = guildById.getTextChannelById(this.channelId);
if (textChannelById == null) {
Logger.warning("Unable to find text channel for reminder, text channel id: [" + channelId + "]");
return null;
}
return textChannelById;
}
public Guild getGuild(JDA jda) {
Guild guildById = jda.getGuildById(guildId);
if (guildById == null) {
Logger.warning("Unable to find guild for reminder, guild id: [" + guildId + "]");
return null;
}
return guildById;
}
}

View File

@ -0,0 +1,67 @@
package com.alttd.database.queries;
import com.alttd.database.Database;
import net.dv8tion.jda.api.entities.Role;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.HashSet;
public class QueriesToggleableRoles {
public static boolean addRoleToggleable(Role role) {
String sql = "INSERT INTO toggleable_roles (guild, role) VALUES (?, ?)";
try {
PreparedStatement preparedStatement = Database.getDatabase().getConnection().prepareStatement(sql);
preparedStatement.setLong(1, role.getGuild().getIdLong());
preparedStatement.setLong(2, role.getIdLong());
return preparedStatement.executeUpdate() == 1;
} catch (SQLException exception) {
exception.printStackTrace();
}
return false;
}
public static boolean removeRoleToggleable(Role role) {
String sql = "DELETE FROM toggleable_roles WHERE guild = ? AND role = ?";
try {
PreparedStatement preparedStatement = Database.getDatabase().getConnection().prepareStatement(sql);
preparedStatement.setLong(1, role.getGuild().getIdLong());
preparedStatement.setLong(2, role.getIdLong());
return preparedStatement.executeUpdate() == 1;
} catch (SQLException exception) {
exception.printStackTrace();
}
return false;
}
public static HashMap<Long, HashSet<Long>> getToggleableRoles() {
String sql = "SELECT * FROM toggleable_roles";
try {
HashMap<Long, HashSet<Long>> map = new HashMap<>();
PreparedStatement preparedStatement = Database.getDatabase().getConnection().prepareStatement(sql);
ResultSet resultSet = preparedStatement.executeQuery();
while (resultSet.next()) {
long guild = resultSet.getLong("guild");
long role = resultSet.getLong("role");
HashSet<Long> roles = map.getOrDefault(guild, new HashSet<>());
roles.add(role);
map.put(guild, roles);
}
return map;
} catch (SQLException exception) {
exception.printStackTrace();
}
return null;
}
}

View File

@ -2,6 +2,9 @@ package com.alttd.database.queries.commandOutputChannels;
import com.alttd.database.Database;
import com.alttd.util.Logger;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.channel.ChannelType;
import net.dv8tion.jda.api.entities.channel.middleman.GuildChannel;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
@ -9,14 +12,15 @@ import java.sql.SQLException;
public class CommandOutputChannels {
public static boolean setOutputChannel(long guildId, OutputType outputType, long channelId) {
String sql = "INSERT INTO output_channels (guild, output_type, channel) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE channel = ?";
public static boolean setOutputChannel(long guildId, OutputType outputType, long channelId, ChannelType channelType) {
String sql = "INSERT INTO output_channels (guild, output_type, channel, channel_type) VALUES (?, ?, ?, ?) ON DUPLICATE KEY UPDATE channel = ?";
try {
PreparedStatement preparedStatement = Database.getDatabase().getConnection().prepareStatement(sql);
preparedStatement.setLong(1, guildId);
preparedStatement.setString(2, outputType.name());
preparedStatement.setLong(3, channelId);
preparedStatement.setLong(4, channelId);
preparedStatement.setString(4, channelType.name());
preparedStatement.setLong(5, channelId);
return preparedStatement.executeUpdate() == 1;
} catch (SQLException e) {
@ -27,26 +31,46 @@ public class CommandOutputChannels {
/**
* Retrieve the channelId of the channel in the specified guild for the specified output type
* @param guildId id of the guild to check in
* @param guild guild to get the channel for
* @param outputType output type to check for
* @return long channel id or 0 if it errors or can't be found
*/
public static long getOutputChannel(long guildId, OutputType outputType) {
String sql = "SELECT channel FROM output_channels WHERE guild = ? AND output_type = ?";
public static GuildChannel getOutputChannel(Guild guild, OutputType outputType) {
String sql = "SELECT channel, channel_type FROM output_channels WHERE guild = ? AND output_type = ?";
try {
PreparedStatement preparedStatement = Database.getDatabase().getConnection().prepareStatement(sql);
preparedStatement.setLong(1, guildId);
preparedStatement.setLong(1, guild.getIdLong());
preparedStatement.setString(2, outputType.name());
ResultSet resultSet = preparedStatement.executeQuery();
if (resultSet.next())
return resultSet.getLong("channel");
else
return 0L;
if (resultSet.next()) {
String stringChannelType = resultSet.getString("channel_type");
ChannelType channelType;
try {
channelType = ChannelType.valueOf(stringChannelType);
} catch (IllegalArgumentException exception) {
return null;
}
long channelId = resultSet.getLong("channel");
switch (channelType) {
case TEXT, NEWS -> {
return guild.getTextChannelById(channelId);
}
case GUILD_NEWS_THREAD, GUILD_PUBLIC_THREAD, GUILD_PRIVATE_THREAD -> {
return guild.getThreadChannelById(channelId);
}
case FORUM -> {
return guild.getForumChannelById(channelId);
}
default -> {
return null;
}
}
}
} catch (SQLException e) {
Logger.exception(e);
return 0L;
}
return null;
}
}

View File

@ -2,10 +2,16 @@ package com.alttd.listeners;
import com.alttd.buttonManager.ButtonManager;
import com.alttd.commandManager.CommandManager;
import com.alttd.contextMenuManager.ContextMenuManager;
import com.alttd.modalManager.ModalManager;
import com.alttd.reminders.ReminderScheduler;
import com.alttd.request.RequestManager;
import com.alttd.util.Logger;
import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.events.ReadyEvent;
import net.dv8tion.jda.api.events.interaction.ModalInteractionEvent;
import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent;
import net.dv8tion.jda.api.events.interaction.component.SelectMenuInteractionEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import org.jetbrains.annotations.NotNull;
@ -22,8 +28,38 @@ public class JDAListener extends ListenerAdapter {
Logger.info("JDA ready to register commands.");
ButtonManager buttonManager = new ButtonManager();
ModalManager modalManager = new ModalManager(buttonManager);
CommandManager commandManager = new CommandManager(jda, modalManager);
jda.addEventListener(buttonManager, modalManager, commandManager);
ContextMenuManager contextMenuManager = new ContextMenuManager(modalManager);
CommandManager commandManager = new CommandManager(jda, modalManager, contextMenuManager);
jda.addEventListener(buttonManager, modalManager, commandManager, contextMenuManager);
ReminderScheduler reminderScheduler = ReminderScheduler.getInstance(jda);
if (reminderScheduler == null) {
Logger.severe("Unable to start reminder scheduler!");
}
// RequestManager.init();
}
@Override
public void onSelectMenuInteraction(@NotNull SelectMenuInteractionEvent event) {
String s = event.getComponentId();
if (s.startsWith("request:")) {
RequestManager.onSelectMenuInteraction(event);
}
}
@Override
public void onModalInteraction(@NotNull ModalInteractionEvent event) {
String s = event.getModalId();
if (s.startsWith("request:")) {
RequestManager.onModalInteractionEvent(event);
}
}
@Override
public void onButtonInteraction(ButtonInteractionEvent event) {
String s = event.getComponentId();
if (s.startsWith("request:")) {
RequestManager.onButtonInteractionEvent(event);
}
}
}

View File

@ -2,6 +2,8 @@ package com.alttd.modalManager;
import com.alttd.buttonManager.ButtonManager;
import com.alttd.modalManager.modals.ModalEvidence;
import com.alttd.modalManager.modals.ModalRemindMe;
import com.alttd.modalManager.modals.ModalReplySuggestion;
import com.alttd.modalManager.modals.ModalSuggestion;
import com.alttd.util.Util;
import net.dv8tion.jda.api.EmbedBuilder;
@ -23,7 +25,9 @@ public class ModalManager extends ListenerAdapter {
public ModalManager(ButtonManager buttonManager) {
modals = List.of(
new ModalSuggestion(buttonManager),
new ModalEvidence());
new ModalEvidence(),
new ModalReplySuggestion(),
new ModalRemindMe(buttonManager));
}
@Override

View File

@ -8,6 +8,7 @@ import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.MessageEmbed;
import net.dv8tion.jda.api.entities.channel.middleman.GuildChannel;
import net.dv8tion.jda.api.entities.channel.middleman.GuildMessageChannel;
import net.dv8tion.jda.api.events.interaction.ModalInteractionEvent;
import net.dv8tion.jda.api.interactions.components.ActionRow;
@ -48,9 +49,15 @@ public class ModalEvidence extends DiscordModal {
return;
}
GuildMessageChannel channel = guild.getChannelById(GuildMessageChannel.class, CommandOutputChannels.getOutputChannel(guild.getIdLong(), OutputType.EVIDENCE));
if (channel == null) {
event.replyEmbeds(Util.genericErrorEmbed("Error", "This guild does not have a suggestion review channel or it's not the right channel type"))
GuildChannel outputChannel = CommandOutputChannels.getOutputChannel(guild, OutputType.EVIDENCE);
if (outputChannel == null) {
event.replyEmbeds(Util.genericErrorEmbed("Error", "This guild does not have an evidence channel or it's not the right channel type"))
.setEphemeral(true).queue(RestAction.getDefaultSuccess(), Util::handleFailure);
return;
}
if (!(outputChannel instanceof GuildMessageChannel channel)) {
event.replyEmbeds(Util.genericErrorEmbed("Error", "Invalid Evidence channel type: " + outputChannel.getType()))
.setEphemeral(true).queue(RestAction.getDefaultSuccess(), Util::handleFailure);
return;
}

View File

@ -0,0 +1,137 @@
package com.alttd.modalManager.modals;
import com.alttd.buttonManager.ButtonManager;
import com.alttd.buttonManager.buttons.remindMeConfirm.ButtonRemindMeConfirm;
import com.alttd.database.queries.QueriesReminders.Reminder;
import com.alttd.modalManager.DiscordModal;
import com.alttd.util.Util;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.entities.MessageEmbed;
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
import net.dv8tion.jda.api.events.interaction.ModalInteractionEvent;
import net.dv8tion.jda.api.interactions.components.ActionRow;
import net.dv8tion.jda.api.interactions.components.Modal;
import net.dv8tion.jda.api.interactions.components.buttons.Button;
import net.dv8tion.jda.api.interactions.components.text.TextInput;
import net.dv8tion.jda.api.interactions.components.text.TextInputStyle;
import net.dv8tion.jda.api.interactions.modals.ModalMapping;
import net.dv8tion.jda.api.requests.RestAction;
import net.dv8tion.jda.api.utils.TimeUtil;
import java.util.Date;
import java.util.HashMap;
import java.util.concurrent.TimeUnit;
public class ModalRemindMe extends DiscordModal {
private static final HashMap<Long, RemindMeData> userToRemindMeMap = new HashMap<>();
public static synchronized void putData(long userId, TextChannel channel, long timestamp) {
userToRemindMeMap.put(userId, new RemindMeData(channel, timestamp));
}
private static synchronized RemindMeData pullData(long userId) {
return userToRemindMeMap.remove(userId);
}
private final ButtonManager buttonManager;
public ModalRemindMe(ButtonManager buttonManager) {
this.buttonManager = buttonManager;
}
@Override
public String getModalId() {
return "remindme";
}
@Override
public void execute(ModalInteractionEvent event) {
String title = getValidString(event.getValue("title"), event, true);
if (title == null)
return;
String desc = getValidString(event.getValue("description"), event, false);
if (desc == null)
desc = "";
long userId = event.getUser().getIdLong();
RemindMeData remindMeData = pullData(userId);
if (remindMeData == null) {
event.replyEmbeds(Util.genericErrorEmbed("Error", "Couldn't find the data from the command that triggered this modal"))
.setEphemeral(true).queue();
return;
}
Reminder reminder = new Reminder(
-1,
title,
desc,
userId,
remindMeData.textChannel.getGuild().getIdLong(),
remindMeData.textChannel.getIdLong(),
0,
false,
new Date().getTime(),
remindMeData.timestamp);
Button remindMeConfirm = buttonManager.getButtonFor("remind_me_confirm");
Button remindMeCancel = buttonManager.getButtonFor("remind_me_cancel");
if (remindMeConfirm == null || remindMeCancel == null) {
event.replyEmbeds(Util.genericErrorEmbed("Error", "Unable to retrieve continue/cancel buttons"))
.setEphemeral(true).queue();
return;
}
MessageEmbed messageEmbed = new EmbedBuilder()
.setTitle(reminder.title())
.setDescription(reminder.description())
.appendDescription("\n\nWill remind <t:" + TimeUnit.MILLISECONDS.toSeconds(reminder.remindDate()) + ":R>")
.build();
event.deferReply().setEphemeral(true).queue(defer -> {
ButtonRemindMeConfirm.putReminder(userId, defer, reminder);
defer.editOriginalEmbeds(messageEmbed).queue(message ->
defer.editOriginalComponents().setActionRow(remindMeConfirm, remindMeCancel)
.queue(RestAction.getDefaultSuccess(), Util::handleFailure));
});
}
public String getValidString(ModalMapping modalMapping, ModalInteractionEvent event, boolean required) {
if (modalMapping == null) {
event.replyEmbeds(Util.genericErrorEmbed("Error", "Couldn't find modal"))
.setEphemeral(true).queue();
return null;
}
String string = modalMapping.getAsString();
if (string.isEmpty()) {
if (required)
event.replyEmbeds(Util.genericErrorEmbed("Error", "Couldn't find contents of modal"))
.setEphemeral(true).queue();
return null;
}
return string;
}
@Override
public Modal getModal() {
TextInput title = TextInput.create("title", "Title", TextInputStyle.SHORT)
.setPlaceholder("reminder title")
.setRequiredRange(1, 256)
.setRequired(true)
.build();
TextInput desc = TextInput.create("description", "Description", TextInputStyle.PARAGRAPH)
.setPlaceholder("optional reminder description")
.setRequiredRange(1, 4000)
.setRequired(false)
.build();
return Modal.create(getModalId(), "Remind Me")
.addActionRows(ActionRow.of(title), ActionRow.of(desc))
.build();
}
private record RemindMeData(TextChannel textChannel, long timestamp) {}
}

View File

@ -0,0 +1,90 @@
package com.alttd.modalManager.modals;
import com.alttd.modalManager.DiscordModal;
import com.alttd.util.Util;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.events.interaction.ModalInteractionEvent;
import net.dv8tion.jda.api.interactions.components.ActionRow;
import net.dv8tion.jda.api.interactions.components.Modal;
import net.dv8tion.jda.api.interactions.components.text.TextInput;
import net.dv8tion.jda.api.interactions.components.text.TextInputStyle;
import net.dv8tion.jda.api.interactions.modals.ModalMapping;
import net.dv8tion.jda.api.requests.RestAction;
import java.util.HashMap;
public class ModalReplySuggestion extends DiscordModal {
private static final HashMap<Long, Message> userToMessageMap = new HashMap<>();
public static synchronized void putMessage(long userId, Message message) {
userToMessageMap.put(userId, message);
}
private static synchronized Message pullMessage(long userId) {
return userToMessageMap.remove(userId);
}
@Override
public String getModalId() {
return "reply_suggestion";
}
@Override
public void execute(ModalInteractionEvent event) {
ModalMapping modalMapping = event.getInteraction().getValue("response");
if (modalMapping == null) {
event.replyEmbeds(Util.genericErrorEmbed("Error", "Unable to find response in modal"))
.setEphemeral(true).queue(RestAction.getDefaultSuccess(), Util::handleFailure);
return;
}
String response = modalMapping.getAsString();
if (response.isEmpty()) {
event.replyEmbeds(Util.genericErrorEmbed("Error", "Response in modal is empty"))
.setEphemeral(true).queue(RestAction.getDefaultSuccess(), Util::handleFailure);
return;
}
Member member = event.getMember();
if (member == null) {
event.replyEmbeds(Util.genericErrorEmbed("Error", "This modal only works from within a guild"))
.setEphemeral(true).queue(RestAction.getDefaultSuccess(), Util::handleFailure);
return;
}
Message message = pullMessage(member.getIdLong());
if (message == null) {
event.replyEmbeds(Util.genericErrorEmbed("Error", "Unable to find a message for this modal"))
.setEphemeral(true).queue(RestAction.getDefaultSuccess(), Util::handleFailure);
return;
}
String[] split = message.getContentRaw().split("\u200B");
if (split.length == 0) {
event.replyEmbeds(Util.genericErrorEmbed("Error", "The suggestion to be edited has no content"))
.setEphemeral(true).queue(RestAction.getDefaultSuccess(), Util::handleFailure);
return;
}
message.editMessage(split[0] + "\u200B\n\n" + "**Response by: " + member.getAsMention() + "**\n_" + response.replaceAll("\u200B", "") + "_")
.queue(success -> event.replyEmbeds(Util.genericSuccessEmbed("Success", "Responded to the suggestion!"))
.setEphemeral(true).queue(RestAction.getDefaultSuccess(), Util::handleFailure),
failure -> event.replyEmbeds(Util.genericErrorEmbed("Error", "Unable to edit the suggestion"))
.setEphemeral(true).queue(RestAction.getDefaultSuccess(), Util::handleFailure));
}
@Override
public Modal getModal() {
TextInput body = TextInput.create("response", "Response", TextInputStyle.PARAGRAPH)
.setPlaceholder("Response...")
.setRequiredRange(10, 1024)
.setRequired(true)
.build();
return Modal.create(getModalId(), "Suggestion Response")
.addActionRows(ActionRow.of(body))
.build();
}
}

View File

@ -10,6 +10,7 @@ import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.MessageEmbed;
import net.dv8tion.jda.api.entities.channel.middleman.GuildChannel;
import net.dv8tion.jda.api.entities.channel.middleman.GuildMessageChannel;
import net.dv8tion.jda.api.events.interaction.ModalInteractionEvent;
import net.dv8tion.jda.api.interactions.components.ActionRow;
@ -44,8 +45,8 @@ public class ModalSuggestion extends DiscordModal {
.setEphemeral(true).queue(RestAction.getDefaultSuccess(), Util::handleFailure);
return;
}
String title = modalMappings.get(0).getAsString();
String desc = modalMappings.get(1).getAsString();
String title = modalMappings.get(0).getAsString().replaceAll("\u200B", "");
String desc = modalMappings.get(1).getAsString().replaceAll("\u200B", "");
Guild guild = event.getGuild();
if (guild == null) {
@ -54,13 +55,19 @@ public class ModalSuggestion extends DiscordModal {
return;
}
GuildMessageChannel channel = guild.getChannelById(GuildMessageChannel.class, CommandOutputChannels.getOutputChannel(guild.getIdLong(), OutputType.SUGGESTION_REVIEW));
if (channel == null) {
GuildChannel outputChannel = CommandOutputChannels.getOutputChannel(guild, OutputType.SUGGESTION_REVIEW);
if (outputChannel == null) {
event.replyEmbeds(Util.genericErrorEmbed("Error", "This guild does not have a suggestion review channel or it's not the right channel type"))
.setEphemeral(true).queue(RestAction.getDefaultSuccess(), Util::handleFailure);
return;
}
if (!(outputChannel instanceof GuildMessageChannel channel)) {
event.replyEmbeds(Util.genericErrorEmbed("Error", outputChannel.getType().name() + " is not a valid suggestion review channel type"))
.setEphemeral(true).queue(RestAction.getDefaultSuccess(), Util::handleFailure);
return;
}
Member member = event.getMember();
if (member == null) {
event.replyEmbeds(Util.genericErrorEmbed("Error", "This command should only be executed from a guild"))
@ -101,7 +108,7 @@ public class ModalSuggestion extends DiscordModal {
@Override
public Modal getModal() {
TextInput title = TextInput.create("title", "Title", TextInputStyle.SHORT)
.setPlaceholder("You suggestion in one sentence")
.setPlaceholder("Your suggestion in one sentence")
.setRequiredRange(10, 100)
.setRequired(true)
.build();

View File

@ -0,0 +1,113 @@
package com.alttd.reminders;
import com.alttd.database.queries.QueriesReminders.QueriesReminders;
import com.alttd.database.queries.QueriesReminders.Reminder;
import com.alttd.util.Logger;
import com.alttd.util.Util;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
import net.dv8tion.jda.api.requests.RestAction;
import net.dv8tion.jda.api.utils.TimeUtil;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Date;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ReminderScheduler {
private static ReminderScheduler instance = null;
private final ArrayList<Reminder> reminders;
private Reminder nextReminder;
private final JDA jda;
private ReminderScheduler(JDA jda) {
instance = this;
this.jda = jda;
reminders = QueriesReminders.getReminders();
if (reminders == null) {
Logger.severe("Unable to retrieve reminders");
instance = null;
return;
}
reminders.sort(Comparator.comparingLong(Reminder::remindDate));
if (reminders.size() == 0)
nextReminder = null;
else
nextReminder = reminders.get(0);
ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
scheduledExecutorService.scheduleWithFixedDelay(new ReminderRun(), 0, 1, TimeUnit.MINUTES);
}
public static ReminderScheduler getInstance(JDA jda) {
if (instance == null)
instance = new ReminderScheduler(jda);
return instance;
}
public synchronized void addReminder(Reminder reminder) {
reminders.add(reminder);
reminders.sort(Comparator.comparingLong(Reminder::remindDate));
nextReminder = reminders.get(0);
}
public synchronized void removeReminder(Reminder reminder) {
reminders.remove(reminder);
if (reminders.size() == 0)
nextReminder = null;
else
nextReminder = reminders.get(0);
QueriesReminders.removeReminder(reminder.id());
}
private class ReminderRun implements Runnable {
@Override
public void run() {
long time = new Date().getTime();
while (nextReminder != null && time > nextReminder.remindDate()) {
//TODO run reminder
TextChannel channel = nextReminder.getChannel(jda);
if (channel == null || !channel.canTalk()) {
Logger.warning("Unable to run reminder: " + nextReminder.id() +
"\ntitle: [" + nextReminder.title() +
"]\ndescription: [" + nextReminder.description() + "]");
return;
}
sendEmbed(nextReminder, channel);
removeReminder(nextReminder);
}
}
private void sendEmbed(Reminder reminder, TextChannel channel) {
EmbedBuilder embedBuilder = new EmbedBuilder()
.setTitle(reminder.title())
.setDescription(reminder.description())
.appendDescription("\n\nRequested <t:" + TimeUnit.MILLISECONDS.toSeconds(reminder.creationDate()) + ":R>");
Guild guild = reminder.getGuild(jda);
if (guild == null) {
sendEmbed(reminder, channel, embedBuilder);
return;
}
guild.retrieveMemberById(reminder.userId()).queue(
member -> sendEmbed(reminder, channel, embedBuilder, member),
failed -> sendEmbed(reminder, channel, embedBuilder));
}
private void sendEmbed(Reminder reminder, TextChannel channel, EmbedBuilder embedBuilder, Member member) {
embedBuilder.setAuthor(member.getEffectiveName(), null, member.getEffectiveAvatarUrl());
channel.sendMessageEmbeds(embedBuilder.build()).queue(RestAction.getDefaultSuccess(), Util::handleFailure);
}
private void sendEmbed(Reminder reminder, TextChannel channel, EmbedBuilder embedBuilder) {
embedBuilder.setAuthor(reminder.userId() + "");
channel.sendMessageEmbeds(embedBuilder.build()).queue(RestAction.getDefaultSuccess(), Util::handleFailure);
}
}
}

View File

@ -0,0 +1,88 @@
package com.alttd.request;
import com.alttd.AltitudeBot;
import com.alttd.util.Pair;
import lombok.AllArgsConstructor;
import lombok.Getter;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
import net.dv8tion.jda.api.entities.channel.concrete.ThreadChannel;
import net.dv8tion.jda.api.interactions.components.ActionRow;
import net.dv8tion.jda.api.interactions.components.Modal;
import net.dv8tion.jda.api.interactions.components.buttons.Button;
import net.dv8tion.jda.api.interactions.components.text.TextInput;
import net.dv8tion.jda.api.interactions.components.text.TextInputStyle;
import net.dv8tion.jda.api.requests.restaction.ThreadChannelAction;
import java.awt.*;
@AllArgsConstructor
public class Request {
// TODO check if all labels on the modal are max 45 in length
@Getter
private String id, category, channel, name, title, description, message;
public Modal modal(Member member) {
TextInput requestTitle = TextInput
.create("title", title, TextInputStyle.SHORT)
.setPlaceholder(id)
.setRequired(false)
.build();
TextInput requestMessage = TextInput
.create("request", message, TextInputStyle.PARAGRAPH)
.build();
return Modal.create("request:" + id, name)
.addActionRow(requestTitle)
.addActionRow(requestMessage)
.build();
}
public void createThread(Member member, String title, String request) {
TextChannel channel = AltitudeBot.getInstance().getJDA().getGuildById(RequestConfig.REQUEST_GUILD_ID).getTextChannelById(getChannel());
if (title == null || title.isEmpty()) title = id;
String finalTitle = title;
ThreadChannelAction threadChannelAction = channel.createThreadChannel(finalTitle);
threadChannelAction.queue(threadChannel -> {
threadChannel.addThreadMember(member).queue();
sendEmbed(threadChannel, finalTitle, request);
channel.deleteMessageById(threadChannel.getId()).queue();
// TODO store the request somewhere so it can be grabbed later
});
}
public void sendEmbed(ThreadChannel channel, String title, String request) {
// Pair<EmbedBuilder, ActionRow> pair = getRequestEmbed(channel.getId(), title, request);
// pairs are not really possible here :(
EmbedBuilder embedBuilder = new EmbedBuilder();
embedBuilder.setTitle(title)
.addField(getName(), request, false)
.setColor(new Color(41, 43, 47));
channel.sendMessageEmbeds(embedBuilder.build()).queue(message1 ->
channel.editMessageEmbedsById(message1.getId(), embedBuilder.build())
.setActionRow(
Button.primary("request:" + getId() + ":" + channel.getId() + ":" + message1.getId() + ":progress", "in progress"),
Button.success("request:" + getId() + ":" + channel.getId() + ":" + message1.getId() + ":complete", "complete"),
Button.danger("request:" + getId() + ":" + channel.getId() + ":" + message1.getId() + ":denied", "denied")
).queue()
);
}
public Pair<EmbedBuilder, ActionRow> getRequestEmbed(String channellId, String title, String request) {
EmbedBuilder embedBuilder = new EmbedBuilder();
embedBuilder.setTitle("title")
.addField(getName(), request, false)
.setColor(new Color(41, 43, 47));
ActionRow actionRow = ActionRow.of(
Button.primary("request:" + getId() + ":" + channellId + ":progress", "in progress"),
Button.success("request:" + getId() + ":" + channellId + ":complete", "complete"),
Button.danger("request:" + getId() + ":" + channellId + ":denied", "denied")
);
return new Pair<>(embedBuilder, actionRow);
}
}

View File

@ -0,0 +1,58 @@
package com.alttd.request;
import com.alttd.config.AbstractConfig;
import com.alttd.util.Logger;
import java.util.ArrayList;
import java.util.List;
public class RequestConfig extends AbstractConfig {
static RequestConfig requestConfig;
public RequestConfig() {
super("requests.yml");
}
public static void reload() {
requestConfig = new RequestConfig();
requestConfig.readConfig(RequestConfig.class, requestConfig);
}
public static String REQUEST_GUILD_ID = "776590138296893480";
public static String REQUEST_CATEGORY = "776590138296893481";
public static String REQUEST_CHANNEL = "1017787342561476709";
public static String REQUEST_MESSAGE = "";
private void settings() {
REQUEST_GUILD_ID = requestConfig.getString("request.guild", REQUEST_GUILD_ID);
REQUEST_CATEGORY = requestConfig.getString("request.category", REQUEST_CATEGORY);
REQUEST_CHANNEL = requestConfig.getString("request.channel", REQUEST_CHANNEL);
REQUEST_MESSAGE = requestConfig.getString("request.message", REQUEST_MESSAGE);
}
public static void setRequestMessage(String messageId) {
REQUEST_MESSAGE = messageId;
requestConfig.update("request.message", REQUEST_MESSAGE);
requestConfig.save();
}
public static final List<Request> requests = new ArrayList<>();
private void loadRequests() {
requests.clear();
requestConfig.getNode("types").childrenMap().forEach((key, value) -> {
String id = key.toString();
String category = value.node("category").getString();
String channel = value.node("channel").getString();
String name = value.node("name").getString();
String title = value.node("title").getString();
String description = value.node("description").getString();
String message = value.node("message").getString();
if (id == null || category == null || channel == null || name == null || description == null || message == null) {
Logger.warning("Requests are set up incorrectly!");
} else {
requests.add(new Request(id, category, channel, name, title, description, message));
}
});
}
}

View File

@ -0,0 +1,110 @@
package com.alttd.request;
import com.alttd.AltitudeBot;
import com.alttd.util.Pair;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
import net.dv8tion.jda.api.entities.channel.concrete.ThreadChannel;
import net.dv8tion.jda.api.events.interaction.ModalInteractionEvent;
import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent;
import net.dv8tion.jda.api.events.interaction.component.SelectMenuInteractionEvent;
import net.dv8tion.jda.api.interactions.components.selections.SelectMenu;
import java.awt.*;
public class RequestManager {
public static void init() {
RequestConfig.reload();
if (RequestConfig.REQUEST_MESSAGE == null || RequestConfig.REQUEST_MESSAGE.isEmpty())
sendRequestMessage();
}
public static Pair<EmbedBuilder, SelectMenu.Builder> getRequestEmbed() {
EmbedBuilder embedBuilder = new EmbedBuilder();
SelectMenu.Builder selectMenuBuilder = SelectMenu.create("request:create");
embedBuilder.setDescription("Select an option below to open a request!\n")
.setTitle("Create a new request.")
.setColor(new Color(41, 43, 47));
for (Request request : RequestConfig.requests) {
embedBuilder.addField(request.getName(), request.getDescription(), false);
selectMenuBuilder.addOption(request.getName(), "request:open:" + request.getId(), request.getDescription(), null);
}
return new Pair<>(embedBuilder, selectMenuBuilder);
}
public static void sendRequestMessage() {
TextChannel channel = AltitudeBot.getInstance().getJDA().getGuildById(RequestConfig.REQUEST_GUILD_ID).getTextChannelById(RequestConfig.REQUEST_CHANNEL);
Pair<EmbedBuilder, SelectMenu.Builder> pair = getRequestEmbed();
channel.sendMessageEmbeds(pair.getValue0().build()).setActionRow(
pair.getValue1().build()
).queue(m -> RequestConfig.setRequestMessage(m.getId()));
}
public static void updateRequestMessage() {
TextChannel channel = AltitudeBot.getInstance().getJDA().getGuildById(RequestConfig.REQUEST_GUILD_ID).getTextChannelById(RequestConfig.REQUEST_CHANNEL);
Pair<EmbedBuilder, SelectMenu.Builder> pair = getRequestEmbed();
channel.editMessageEmbedsById(RequestConfig.REQUEST_MESSAGE, pair.getValue0().build())
.setActionRow(
pair.getValue1().build()
).queue(m -> RequestConfig.setRequestMessage(m.getId()));
}
public static Request getRequestById(String id) {
return RequestConfig.requests.stream().filter(request -> request.getId().equalsIgnoreCase(id)).findFirst().orElse(null);
}
public static void onSelectMenuInteraction(SelectMenuInteractionEvent event) {
String[] actions = event.getComponentId().split(":");
if (actions[1].equals("create")) {
String[] selection = event.getSelectedOptions().get(0).getValue().split(":");
if (selection[0].equals("request") && selection[1].equals("open")) {
String id = selection[2];
event.replyModal(getRequestById(id).modal(event.getMember())).queue();
updateRequestMessage(); // You can't use a select menu option twice in a row, updating it fixes that.
}
}
}
public static void onModalInteractionEvent(ModalInteractionEvent event) {
String s = event.getModalId();
String[] strings = s.split(":", 2);
getRequestById(strings[1]).createThread(event.getMember(), event.getValue("title").getAsString(), event.getValue("request").getAsString());
event.reply("Thanks for your request!").setEphemeral(true).queue();
}
public static void onButtonInteractionEvent(ButtonInteractionEvent event) {
String s = event.getComponentId();
String[] strings = s.split(":", 5);
String requestId = strings[1];
String threadId = strings[2];
String messageId = strings[3];
String type = strings[4]; // progress, complete, denied
// TODO update the stored request in the database
switch (type) {
case "denied" -> {
// TODO open a new modal to input a reason?
// could also do this by command?
event.reply("This request has been denied by " + event.getMember().getAsMention()).queue();
ThreadChannel threadChannel = AltitudeBot.getInstance().getJDA().getGuildById(RequestConfig.REQUEST_GUILD_ID).getThreadChannelById(threadId);
threadChannel.getManager().setArchived(true).setLocked(true).queue();
}
case "complete" -> {
// TODO open a new modal to input a reason?
// could also do this by command?
event.reply("This request has been completed by " + event.getMember().getAsMention()).queue();
ThreadChannel threadChannel = AltitudeBot.getInstance().getJDA().getGuildById(RequestConfig.REQUEST_GUILD_ID).getThreadChannelById(threadId);
threadChannel.getManager().setArchived(true).setLocked(true).queue();
}
case "progress" -> {
// TODO open a new modal to input a reason?
// edit the message to show who is working on it?
}
}
}
}

View File

@ -0,0 +1,21 @@
package com.alttd.util;
public class Pair<X, Y> {
private final X x;
private final Y y;
public Pair(X x, Y y) {
this.x = x;
this.y = y;
}
public X getValue0() {
return x;
}
public Y getValue1() {
return y;
}
}