package com.alttd.commandManager.commands; import com.alttd.AltitudeBot; import com.alttd.commandManager.CommandManager; import com.alttd.commandManager.DiscordCommand; import com.alttd.database.queries.QueriesAuctions.Auction; import com.alttd.database.queries.commandOutputChannels.CommandOutputChannels; import com.alttd.database.queries.commandOutputChannels.OutputType; import com.alttd.schedulers.AuctionScheduler; import com.alttd.selectMenuManager.SelectMenuManager; 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.Message; import net.dv8tion.jda.api.entities.MessageEmbed; 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.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.interactions.components.selections.SelectMenu; import net.dv8tion.jda.api.requests.RestAction; import net.dv8tion.jda.api.requests.restaction.interactions.ReplyCallbackAction; import net.dv8tion.jda.api.utils.AttachedFile; import java.awt.*; import java.nio.file.Path; import java.time.Instant; import java.util.UUID; import java.util.concurrent.TimeUnit; public class CommandAuction extends DiscordCommand { private final CommandData commandData; private final SelectMenuManager selectMenuManager; public CommandAuction(JDA jda, CommandManager commandManager, SelectMenuManager selectMenuManager) { commandData = Commands.slash(getName(), "Create an auction") .addOption(OptionType.STRING, "item", "The name (and type) of the item you're selling", true) .addOption(OptionType.INTEGER, "amount", "How many of the item you're selling", true) .addOption(OptionType.INTEGER, "starting-price", "How much the bids should start at (the minimum price you're willing to accept for the item", true) .addOption(OptionType.INTEGER, "minimum-increase", "The minimum amount of money bids should increase by", true) .addOption(OptionType.INTEGER, "insta-buy", "(optional) A price you're willing to sell the item for immediately (ends the auction)", false) .addOption(OptionType.STRING, "description", "(optional) A further explanation of the item", false) .addOption(OptionType.ATTACHMENT, "screenshot", "(optional) A screenshot of the item you're selling if needed", false) .setDefaultPermissions(DefaultMemberPermissions.ENABLED); this.selectMenuManager = selectMenuManager; Util.registerCommand(commandManager, jda, commandData, getName()); } @Override public String getName() { return "auction"; } @Override public void execute(SlashCommandInteractionEvent event) { Guild guild = event.getGuild(); if (guild == null) { handleError(event, "This command can only be executed within a guild"); return; } GuildChannel outputChannel = CommandOutputChannels.getOutputChannel(guild, OutputType.AUCTION); if (outputChannel == null) { handleError(event, "This guild does not have an Auction channel set"); return; } TextChannel textChannel = validTextChannel(event, outputChannel); if (textChannel == null) { handleError(event, "This guild has an invalid Auction channel"); return; } Integer minimumIncrease = event.getOption("minimum-increase", OptionMapping::getAsInt); if (minimumIncrease == null) { handleError(event, "Missing required minimum increase option"); return; } MessageEmbed messageEmbed = buildAuctionEmbed(event, minimumIncrease); if (messageEmbed == null) return; ReplyCallbackAction replyCallbackAction = event.deferReply(true); textChannel.sendMessageEmbeds(messageEmbed).queue( success -> onSuccessSendAuctionMessage(event, success, replyCallbackAction, minimumIncrease), error -> replyCallbackAction.setEmbeds(Util.genericErrorEmbed("Error", "Unable to send your auction to the auction channel")) .queue()); } private void onSuccessSendAuctionMessage(SlashCommandInteractionEvent event, Message message, ReplyCallbackAction replyCallbackAction, int minimumIncrease) { Message.Attachment screenshot = event.getOption("screenshot", OptionMapping::getAsAttachment); if (screenshot != null) addScreenshot(screenshot, message); Integer startingPrice = event.getOption("starting-price", OptionMapping::getAsInt); if (startingPrice == null) { Logger.altitudeLogs.error("Starting price magically became null"); replyCallbackAction.setEmbeds(Util.genericSuccessEmbed("Error", "Failed to store auction")) .queue(); return; } Auction auction = new Auction( event.getUser().getIdLong(), message, message.getChannel().getIdLong(), message.getGuild().getIdLong(), startingPrice, Instant.now().toEpochMilli() + TimeUnit.DAYS.toMillis(1), minimumIncrease, event.getOption("insta-buy", OptionMapping::getAsInt)); SelectMenu selectMenu = auction.getSelectMenu(selectMenuManager, false); if (selectMenu == null) { replyCallbackAction.setEmbeds(Util.genericErrorEmbed("Error", "Unable to find select menu for your auction, removing message...")) .queue(); message.delete().queue(); return; } message.editMessageComponents().setActionRow(selectMenu).queue(); AuctionScheduler auctionScheduler = AuctionScheduler.getInstance(); if (auctionScheduler == null) { replyCallbackAction.setEmbeds(Util.genericSuccessEmbed("Error", "Failed to store auction in scheduler")) .queue(); return; } auctionScheduler.addAuction(auction); replyCallbackAction.setEmbeds(Util.genericSuccessEmbed("Success", "Your auction was created")) .queue(); } private void addScreenshot(Message.Attachment screenshot, Message message) { String dataFolder = AltitudeBot.getInstance().getDataFolder(); Path path = Path.of(dataFolder + UUID.randomUUID() + "." + screenshot.getFileExtension()); screenshot.getProxy().downloadToFile(path.toFile()).whenComplete((file, throwable) -> message.editMessageAttachments(AttachedFile.fromData(file)).queue(done -> file.delete(), failed -> { Util.handleFailure(failed); file.delete(); })) .exceptionally(e -> { e.printStackTrace(); return null; }); } private MessageEmbed buildAuctionEmbed(SlashCommandInteractionEvent event, int minimumIncrease) { Member member = event.getMember(); if (member == null) { return handleBuildEmbedError(event, "You are not a member of this guild"); } EmbedBuilder embedBuilder = new EmbedBuilder(); String item = event.getOption("item", OptionMapping::getAsString); if (item == null) return handleBuildEmbedError(event, "Missing required item option"); if (item.length() > 128) return handleBuildEmbedError(event, "Your item name is too long"); embedBuilder.appendDescription("**Item**: " + item); Integer amount = event.getOption("amount", OptionMapping::getAsInt); if (amount == null) return handleBuildEmbedError(event, "Missing required amount option"); embedBuilder.appendDescription("\n**Amount**: " + Util.formatNumber(amount)); Integer startingPrice = event.getOption("starting-price", OptionMapping::getAsInt); if (startingPrice == null) return handleBuildEmbedError(event, "Missing required starting price option"); embedBuilder.appendDescription("\n**Starting Price**: $" + Util.formatNumber(startingPrice)); Integer instaBuy = event.getOption("insta-buy", OptionMapping::getAsInt); if (instaBuy != null) { if (instaBuy == minimumIncrease) return handleBuildEmbedError(event, "Insta buy can't be the same as minimum increase"); embedBuilder.appendDescription("\n**Insta Buy**: $" + Util.formatNumber(instaBuy)); } String description = event.getOption("description", OptionMapping::getAsString); if (description != null) embedBuilder.appendDescription("\n**Description**: " + description); embedBuilder .setAuthor(member.getEffectiveName(), null, member.getAvatarUrl()) .setTitle("Auction") .setColor(Color.ORANGE) .appendDescription("\n\nCloses "); return embedBuilder.build(); } private MessageEmbed handleBuildEmbedError(SlashCommandInteractionEvent event, String s) { event.replyEmbeds(Util.genericErrorEmbed("Error", s)) .setEphemeral(true) .queue(); return null; } private void handleError(SlashCommandInteractionEvent event, String s) { event.replyEmbeds(Util.genericErrorEmbed("Error", s)) .setEphemeral(true) .queue(); } private TextChannel validTextChannel(SlashCommandInteractionEvent event, GuildChannel guildChannel) { if (guildChannel == null) { event.replyEmbeds(Util.genericErrorEmbed("Error", "This server does not have a valid auction channel")) .setEphemeral(true).queue(RestAction.getDefaultSuccess(), Util::handleFailure); return null; } if (!(guildChannel instanceof TextChannel channel)) { event.replyEmbeds(Util.genericErrorEmbed("Error", "A auction channel can't be of type: " + guildChannel.getType().name())) .setEphemeral(true).queue(RestAction.getDefaultSuccess(), Util::handleFailure); return null; } return channel; } @Override public void suggest(CommandAutoCompleteInteractionEvent event) { } @Override public String getHelpMessage() { return null; } @Override public CommandData getCommandData() { return commandData; } }