Added auction command

This commit is contained in:
Teriuihi 2022-10-21 02:59:00 +02:00
parent df47c7a709
commit d6eb332d83
13 changed files with 874 additions and 12 deletions

View File

@ -3,7 +3,7 @@ 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.schedulers.ReminderScheduler;
import com.alttd.util.Util;
import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent;
import net.dv8tion.jda.api.interactions.InteractionHook;

View File

@ -7,6 +7,7 @@ import com.alttd.contextMenuManager.ContextMenuManager;
import com.alttd.database.Database;
import com.alttd.listeners.ChatListener;
import com.alttd.modalManager.ModalManager;
import com.alttd.selectMenuManager.SelectMenuManager;
import com.alttd.util.Logger;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.JDA;
@ -31,7 +32,7 @@ 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, ContextMenuManager contextMenuManager, ChatListener chatListener) {
public CommandManager(JDA jda, ModalManager modalManager, ContextMenuManager contextMenuManager, ChatListener chatListener, SelectMenuManager selectMenuManager) {
commandList.put("manage", new ArrayList<>(List.of(new ScopeInfo(CommandScope.GLOBAL, 0))));
loadCommands();
Logger.info("Loading commands...");
@ -51,7 +52,8 @@ public class CommandManager extends ListenerAdapter {
commandSetToggleableRoles,
new CommandToggleRole(commandSetToggleableRoles, jda, this),
new CommandRemindMe(jda, this, modalManager),
new CommandSoftLock(jda, this, chatListener));
new CommandSoftLock(jda, this, chatListener),
new CommandAuction(jda, this, selectMenuManager));
}
@Override

View File

@ -0,0 +1,254 @@
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.QueriesAuctions.QueriesAuction;
import com.alttd.database.queries.commandOutputChannels.CommandOutputChannels;
import com.alttd.database.queries.commandOutputChannels.OutputType;
import com.alttd.schedulers.AuctionScheduler;
import com.alttd.selectMenuManager.DiscordSelectMenu;
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.interactions.components.selections.SelectOption;
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.io.File;
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 -> {
Message.Attachment screenshot = event.getOption("screenshot", OptionMapping::getAsAttachment);
if (screenshot != null) {
String dataFolder = AltitudeBot.getInstance().getDataFolder();
Path path = Path.of(dataFolder + File.separator + UUID.randomUUID() + "." + screenshot.getFileExtension());
screenshot.getProxy().downloadToFile(path.toFile()).thenAccept(file ->
success.editMessageAttachments(AttachedFile.fromData(file)).queue(done -> file.delete(), failed -> {
Util.handleFailure(failed);
file.delete();
}))
.exceptionally(e -> {
e.printStackTrace();
return null;
});
}
DiscordSelectMenu discordAuction = selectMenuManager.getDiscordSelectMenuFor("auction");
if (discordAuction == null) {
replyCallbackAction.setEmbeds(Util.genericErrorEmbed("Error", "Unable to find select menu for your auction, removing message..."))
.queue();
success.delete().queue();
return;
}
SelectMenu selectMenu;
Integer instaBuy = event.getOption("insta-buy", OptionMapping::getAsInt);
int mediumIncrease = minimumIncrease * 5;
if (instaBuy != null) {
if (mediumIncrease == instaBuy)
mediumIncrease += 1;
selectMenu = discordAuction.getSelectMenu(
SelectOption.of("Increase bid by: " + minimumIncrease, "" + minimumIncrease),
SelectOption.of("Increase bid by: " + mediumIncrease, "" + mediumIncrease),
SelectOption.of("Insta Buy: " + instaBuy, "" + instaBuy)
);
} else {
selectMenu = discordAuction.getSelectMenu(
SelectOption.of("Increase bid by: " + minimumIncrease, "" + minimumIncrease),
SelectOption.of("Increase bid by: " + mediumIncrease, "" + mediumIncrease)
);
}
if (selectMenu == null) {
replyCallbackAction.setEmbeds(Util.genericErrorEmbed("Error", "Unable to add select menu to your auction, removing message..."))
.queue();
success.delete().queue();
return;
}
success.editMessageComponents().setActionRow(selectMenu).queue();
Integer startingPrice = event.getOption("starting-price", OptionMapping::getAsInt);
if (startingPrice == null) {
Logger.severe("Starting price magically became null");
replyCallbackAction.setEmbeds(Util.genericSuccessEmbed("Error", "Failed to store auction"))
.queue();
return;
}
AuctionScheduler auctionScheduler = AuctionScheduler.getInstance();
if (auctionScheduler == null) {
replyCallbackAction.setEmbeds(Util.genericSuccessEmbed("Error", "Failed to store auction in scheduler"))
.queue();
return;
}
auctionScheduler.addAuction(new Auction(
success,
success.getChannel().getIdLong(),
success.getGuild().getIdLong(),
startingPrice,
Instant.now().toEpochMilli() + TimeUnit.DAYS.toMillis(1)));
replyCallbackAction.setEmbeds(Util.genericSuccessEmbed("Success", "Your auction was created"))
.queue();
}, error -> replyCallbackAction.setEmbeds(Util.genericErrorEmbed("Error", "Unable to send your auction to the auction channel"))
.queue());
}
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**: " + 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**: $" + 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**: $" + 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 <t:" + (Instant.now().getEpochSecond() + TimeUnit.DAYS.toSeconds(1)) + ":R>");
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;
}
}

View File

@ -135,6 +135,23 @@ public class DatabaseTables {
}
}
private void createAuctionTable() {
String sql = "CREATE TABLE IF NOT EXISTS auctions(" +
"message_id BIGINT NOT NULL, " +
"channel_id BIGINT NOT NULL, " +
"guild_id BIGINT NOT NULL, " +
"starting_price INT NOT NULL, " +
"expire_time BIGINT NOT NULL, " +
"PRIMARY KEY (message_id)" +
")";
try {
connection.prepareStatement(sql).executeUpdate();
} catch (SQLException e) {
Logger.sql(e);
Logger.severe("Unable to create auction table, shutting down...");
}
}
public static void createTables(Connection connection) {
if (instance == null)
instance = new DatabaseTables(connection);

View File

@ -0,0 +1,91 @@
package com.alttd.database.queries.QueriesAuctions;
import com.alttd.AltitudeBot;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
import org.jetbrains.annotations.NotNull;
import javax.annotation.Nullable;
import java.time.Instant;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
public class Auction implements Comparable {
private final long messageId, channelId, guildId;
private final int startingPrice;
private long expireTime;
public Auction(long messageId, long channelId, long guildId, int startingPrice, long expireTime) {
this.messageId = messageId;
this.channelId = channelId;
this.guildId = guildId;
this.startingPrice = startingPrice;
this.expireTime = expireTime;
}
public Auction(@NotNull Message message, long channelId, long guildId, int startingPrice, long expireTime) {
this.messageId = message.getIdLong();
this.channelId = channelId;
this.guildId = guildId;
this.startingPrice = startingPrice;
this.expireTime = expireTime;
}
public void updateMessage(Consumer<? super Message> success, @Nullable Consumer<? super String> failure) {
Guild guild = AltitudeBot.getInstance().getJDA().getGuildById(guildId);
if (guild == null) {
if (failure != null)
failure.accept("Unable to retrieve auction message due to invalid guild");
return;
}
TextChannel textChannel = guild.getTextChannelById(channelId);
if (textChannel == null) {
if (failure != null)
failure.accept("Unable to retrieve auction message due to invalid text channel");
return;
}
textChannel.retrieveMessageById(messageId).queue(success, b -> {
if (failure != null)
failure.accept("Unable to retrieve the auction message");
});
}
public long getExpireTime() {
return expireTime;
}
public long getMessageId() {
return messageId;
}
public long getChannelId() {
return channelId;
}
public long getGuildId() {
return guildId;
}
public int getStartingPrice() {
return startingPrice;
}
public boolean updateExpiry() {
long future = Instant.now().toEpochMilli() + TimeUnit.MINUTES.toMillis(5);
if (expireTime < future) {
expireTime = future;
return true;
}
return false;
}
@Override
public int compareTo(@NotNull Object o) {
Auction o1 = (Auction) o;
return Long.compare(expireTime, o1.getExpireTime());
}
}

View File

@ -0,0 +1,67 @@
package com.alttd.database.queries.QueriesAuctions;
import com.alttd.database.Database;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
public class QueriesAuction {
public static HashMap<Long, Auction> getAuctions() {
HashMap<Long, Auction> auctions = new HashMap<>();
String sql = "SELECT * FROM auctions";
try {
PreparedStatement statement = Database.getDatabase().getConnection().prepareStatement(sql);
ResultSet resultSet = statement.executeQuery();
while (resultSet.next()) {
long messageId = resultSet.getLong("message_id");
long channelId = resultSet.getLong("channel_id");
long guildId = resultSet.getLong("guild_id");
int startingPrice = resultSet.getInt("starting_price");
long expireTime = resultSet.getLong("expire_time");
auctions.put(messageId, new Auction(messageId, channelId, guildId, startingPrice, expireTime));
}
} catch (SQLException exception) {
exception.printStackTrace();
return null;
}
return auctions;
}
public static boolean saveAuction(Auction auction) {
String sql = "INSERT INTO auctions " +
"(message_id, channel_id, guild_id, starting_price, expire_time) " +
"VALUES (?, ?, ?, ?, ?) " +
"ON DUPLICATE KEY UPDATE expire_time = ?";
try {
PreparedStatement statement = Database.getDatabase().getConnection().prepareStatement(sql);
statement.setLong(1, auction.getMessageId());
statement.setLong(2, auction.getChannelId());
statement.setLong(3, auction.getGuildId());
statement.setInt(4, auction.getStartingPrice());
statement.setLong(5, auction.getExpireTime());
statement.setLong(6, auction.getExpireTime());
return statement.executeUpdate() == 1;
} catch (SQLException exception) {
exception.printStackTrace();
}
return false;
}
public static boolean removeAuction(Auction auction) {
String sql = "DELETE FROM auctions " +
"WHERE message_id = ?";
try {
PreparedStatement statement = Database.getDatabase().getConnection().prepareStatement(sql);
statement.setLong(1, auction.getMessageId());
return statement.executeUpdate() == 1;
} catch (SQLException exception) {
exception.printStackTrace();
}
return false;
}
}

View File

@ -5,5 +5,7 @@ public enum OutputType {
SUGGESTION_REVIEW,
MOD_LOG,
EVIDENCE,
AUCTION,
AUCTION_LOG,
CRATE_TEAM
}

View File

@ -4,8 +4,10 @@ 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.schedulers.AuctionScheduler;
import com.alttd.schedulers.ReminderScheduler;
import com.alttd.request.RequestManager;
import com.alttd.selectMenuManager.SelectMenuManager;
import com.alttd.util.Logger;
import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.events.ReadyEvent;
@ -30,15 +32,23 @@ public class JDAListener extends ListenerAdapter {
ButtonManager buttonManager = new ButtonManager();
ModalManager modalManager = new ModalManager(buttonManager);
ContextMenuManager contextMenuManager = new ContextMenuManager(modalManager);
CommandManager commandManager = new CommandManager(jda, modalManager, contextMenuManager, chatListener);
jda.addEventListener(buttonManager, modalManager, commandManager, contextMenuManager, chatListener);
ReminderScheduler reminderScheduler = ReminderScheduler.getInstance(jda);
if (reminderScheduler == null) {
Logger.severe("Unable to start reminder scheduler!");
}
SelectMenuManager selectMenuManager = new SelectMenuManager();
CommandManager commandManager = new CommandManager(jda, modalManager, contextMenuManager, chatListener, selectMenuManager);
jda.addEventListener(buttonManager, modalManager, commandManager, contextMenuManager, chatListener, selectMenuManager);
startSchedulers();
// RequestManager.init();
}
private void startSchedulers() {
ReminderScheduler reminderScheduler = ReminderScheduler.getInstance(jda);
if (reminderScheduler == null)
Logger.severe("Unable to start reminder scheduler!");
AuctionScheduler auctionScheduler = AuctionScheduler.getInstance();
if (auctionScheduler == null)
Logger.severe("Unable to start auction scheduler!");
}
@Override
public void onSelectMenuInteraction(@NotNull SelectMenuInteractionEvent event) {
String s = event.getComponentId();

View File

@ -0,0 +1,135 @@
package com.alttd.schedulers;
import com.alttd.database.queries.QueriesAuctions.Auction;
import com.alttd.database.queries.QueriesAuctions.QueriesAuction;
import com.alttd.database.queries.commandOutputChannels.CommandOutputChannels;
import com.alttd.database.queries.commandOutputChannels.OutputType;
import com.alttd.util.Logger;
import com.alttd.util.Util;
import net.dv8tion.jda.api.EmbedBuilder;
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 javax.annotation.Nullable;
import java.awt.*;
import java.util.*;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class AuctionScheduler {
private static AuctionScheduler instance = null;
private final HashMap<Long, Auction> auctions;
private Auction nextAuction;
private AuctionScheduler() {
instance = this;
auctions = QueriesAuction.getAuctions();
if (auctions == null) {
Logger.severe("Unable to retrieve auctions");
instance = null;
return;
}
setNextAuction();
ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
scheduledExecutorService.scheduleWithFixedDelay(new AuctionScheduler.AuctionRun(), 0, 1, TimeUnit.SECONDS);
}
private void setNextAuction() {
Optional<Auction> first = auctions.values().stream().sorted().findFirst();
if (first.isEmpty())
nextAuction = null;
else
nextAuction = first.get();
}
public static AuctionScheduler getInstance() {
if (instance == null)
instance = new AuctionScheduler();
return instance;
}
public synchronized void addAuction(Auction auction) {
if (!QueriesAuction.saveAuction(auction))
Logger.warning("Unable to save auction %", auction.getMessageId() + "");
auctions.put(auction.getMessageId(), auction);
setNextAuction();
}
public synchronized void updateAuction(Auction auction) {
QueriesAuction.saveAuction(auction);
setNextAuction();
}
public synchronized void removeAuction(Auction auction) {
auctions.remove(auction.getMessageId());
setNextAuction();
if (!QueriesAuction.removeAuction(auction))
Logger.warning("Unable to remove auction %", auction.getMessageId() + "");
}
public Auction getAuction(long messageId) {
return auctions.getOrDefault(messageId, null);
}
public synchronized void finishAuction(Auction auction, @Nullable Member instaBuy) {
auction.updateMessage(success -> {
List<MessageEmbed> embeds = success.getEmbeds();
if (embeds.isEmpty()) {
Logger.warning("Received auction with no embed contents");
return;
}
GuildChannel outputChannel = CommandOutputChannels.getOutputChannel(success.getGuild(), OutputType.AUCTION_LOG);
if (outputChannel != null) {
if (!(outputChannel instanceof GuildMessageChannel channel)) {
Logger.warning("Error" + outputChannel.getType().name() + " is not a valid crate auction log channel type");
return;
}
if (!channel.canTalk()) {
Logger.warning("Error can't talk in auction log channel");
return;
}
if (sendEmbed(embeds.get(0), channel, instaBuy))
success.delete().queue();
} else
success.delete().queue();
}, Logger::warning);
removeAuction(auction);
}
private boolean sendEmbed(MessageEmbed embed, GuildMessageChannel textChannel, Member instaBuy) {
EmbedBuilder embedBuilder = new EmbedBuilder(embed)
.clearFields();
List<MessageEmbed.Field> fields = embed.getFields();
if (instaBuy != null)
embedBuilder.addField("Winning Bid", "Insta bought by " + instaBuy.getAsMention(), false);
else if (!fields.isEmpty()) {
MessageEmbed.Field field = fields.get(0);
if (field.getName() != null && field.getName().equals("Current Bid") && field.getValue() != null) {
embedBuilder.addField("Winning Bid", field.getValue(), false);
} else if (fields.size() == 2) {
field = fields.get(1);
if (field.getName() != null && field.getName().equals("Current Bid") && field.getValue() != null)
embedBuilder.addField("Winning Bid", field.getValue(), false);
}
}
if (embedBuilder.getFields().size() != 0)
embedBuilder.setColor(Color.GREEN);
else
embedBuilder.setColor(Color.RED);
textChannel.sendMessageEmbeds(embedBuilder.build()).queue(Util::ignoreSuccess, failure -> Logger.warning("Failed to log auction result"));
return true;
}
private class AuctionRun implements Runnable {
@Override
public void run() {
long time = new Date().getTime();
while (nextAuction != null && time > nextAuction.getExpireTime()) {
finishAuction(nextAuction, null);
}
}
}
}

View File

@ -1,4 +1,4 @@
package com.alttd.reminders;
package com.alttd.schedulers;
import com.alttd.database.queries.QueriesReminders.QueriesReminders;
import com.alttd.database.queries.QueriesReminders.Reminder;
@ -10,7 +10,6 @@ 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;

View File

@ -0,0 +1,16 @@
package com.alttd.selectMenuManager;
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.buttons.Button;
import net.dv8tion.jda.api.interactions.components.selections.SelectMenu;
import net.dv8tion.jda.api.interactions.components.selections.SelectOption;
public abstract class DiscordSelectMenu {
public abstract String getSelectMenuId();
public abstract void execute(SelectMenuInteractionEvent event);
public abstract SelectMenu getSelectMenu(SelectOption ...selectOptions);
}

View File

@ -0,0 +1,52 @@
package com.alttd.selectMenuManager;
import com.alttd.selectMenuManager.selectMenus.SelectMenuAuction;
import com.alttd.util.Util;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.events.interaction.component.SelectMenuInteractionEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import net.dv8tion.jda.api.requests.RestAction;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.awt.*;
import java.util.List;
import java.util.Optional;
public class SelectMenuManager extends ListenerAdapter {
private final List<DiscordSelectMenu> buttons;
public SelectMenuManager() {
buttons = List.of(new SelectMenuAuction());
}
@Override
public void onSelectMenuInteraction(@NotNull SelectMenuInteractionEvent event) {
String selectMenuId = event.getSelectMenu().getId();
Optional<DiscordSelectMenu> first = buttons.stream()
.filter(discordModal -> discordModal.getSelectMenuId().equalsIgnoreCase(selectMenuId))
.findFirst();
if (first.isEmpty()) {
event.replyEmbeds(new EmbedBuilder()
.setTitle("Invalid command")
.setDescription("Unable to process select menu with id: [" + selectMenuId + "], please report this issue to a Teri")
.setColor(Color.RED)
.build())
.setEphemeral(true)
.queue(RestAction.getDefaultSuccess(), Util::handleFailure);
return;
}
first.get().execute(event);
}
public @Nullable DiscordSelectMenu getDiscordSelectMenuFor(String buttonId) {
Optional<DiscordSelectMenu> first = buttons.stream()
.filter(discordSelectMenu -> discordSelectMenu.getSelectMenuId().equalsIgnoreCase(buttonId))
.findFirst();
if (first.isEmpty())
return null;
return first.get();
}
}

View File

@ -0,0 +1,217 @@
package com.alttd.selectMenuManager.selectMenus;
import com.alttd.database.queries.QueriesAuctions.Auction;
import com.alttd.database.queries.QueriesAuctions.QueriesAuction;
import com.alttd.schedulers.AuctionScheduler;
import com.alttd.selectMenuManager.DiscordSelectMenu;
import com.alttd.util.Util;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Mentions;
import net.dv8tion.jda.api.entities.MessageEmbed;
import net.dv8tion.jda.api.events.interaction.component.SelectMenuInteractionEvent;
import net.dv8tion.jda.api.interactions.components.selections.SelectMenu;
import net.dv8tion.jda.api.interactions.components.selections.SelectOption;
import net.dv8tion.jda.api.requests.restaction.interactions.ReplyCallbackAction;
import java.awt.*;
import java.time.Instant;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
public class SelectMenuAuction extends DiscordSelectMenu {
@Override
public String getSelectMenuId() {
return "auction";
}
@Override
public void execute(SelectMenuInteractionEvent event) {
Member member = event.getMember();
if (member == null) {
event.replyEmbeds(Util.genericErrorEmbed("Error", "This can only be used in a guild"))
.setEphemeral(true).queue();
return;
}
AuctionScheduler auctionScheduler = AuctionScheduler.getInstance();
if (auctionScheduler == null) {
event.replyEmbeds(Util.genericErrorEmbed("Error", "Unable to get scheduler"))
.setEphemeral(true).queue();
return;
}
Auction auction = auctionScheduler.getAuction(event.getMessage().getIdLong());
if (auction == null) {
event.replyEmbeds(Util.genericErrorEmbed("Error", "Unable to find this auction in the database, please try again"))
.setEphemeral(true).queue();
return;
}
List<SelectOption> collect = event.getInteraction().getSelectedOptions().stream().filter(opt -> !opt.isDefault()).collect(Collectors.toList());
if (collect.isEmpty()) {
event.replyEmbeds(Util.genericErrorEmbed("Error", "Received default input"))
.setEphemeral(true).queue();
return;
}
if (collect.size() != 1) {
event.replyEmbeds(Util.genericErrorEmbed("Error", "Received invalid number of inputs, can only handle one"))
.setEphemeral(true).queue();
return;
}
SelectOption selectOption = collect.get(0);
String value = selectOption.getValue();
int bid;
try {
bid = Integer.parseInt(value);
} catch (NumberFormatException e) {
event.replyEmbeds(Util.genericErrorEmbed("Error", "Received invalid input"))
.setEphemeral(true).queue();
return;
}
MessageEmbed messageEmbed = getMessageEmbed(event.getMessage().getEmbeds(), event);
if (messageEmbed == null)
return;
BidFieldInfo bidFieldInfo = getPreviousBid(messageEmbed, event);
if (bidFieldInfo == null)
return;
int prevBid;
if (bidFieldInfo.bid() == 0)
prevBid = auction.getStartingPrice();
else
prevBid = bidFieldInfo.bid();
int currentBid = prevBid + bid;
if (selectOption.getLabel().startsWith("Insta Buy")) {
auctionScheduler.finishAuction(auction, member);
event.replyEmbeds(Util.genericSuccessEmbed("Success", "You successfully insta bought the item for " + bid + "!"))
.setEphemeral(true).queue();
return;
}
EmbedBuilder embedBuilder = new EmbedBuilder(messageEmbed)
.clearFields()
.setTimestamp(Instant.now());
if (auction.updateExpiry()) {
auctionScheduler.updateAuction(auction);
String description = messageEmbed.getDescription();
if (description != null)
embedBuilder.setDescription(description.substring(0, description.lastIndexOf("Closes <t:")))
.appendDescription("Closes <t:" + TimeUnit.MILLISECONDS.toSeconds(auction.getExpireTime()) + ":R>");
}
if (bidFieldInfo.member() != null)
embedBuilder.addField("Previous Bid", "$" + bidFieldInfo.bid() + " by <@" + bidFieldInfo.member() + ">", false);
embedBuilder.addField("Current Bid", "$" + currentBid + " by " + event.getMember().getAsMention(), false);
ReplyCallbackAction replyCallbackAction = event.deferReply(true);
int finalPrevBid = prevBid;
event.getMessage().editMessageEmbeds(embedBuilder.build()).queue(
success -> {
if (auction.updateExpiry())
auctionScheduler.updateAuction(auction);
replyCallbackAction.setEmbeds(Util.genericSuccessEmbed("Success", "You successfully increased the bid from " + finalPrevBid + " to " + currentBid + "!"))
.queue();
},
error -> replyCallbackAction.setEmbeds(Util.genericErrorEmbed("Error", "Unable to finish your bid")).queue());
}
/**
* Expecting to find:
* a: No fields
* b: One field containing the current bid
* c: Two fields, one containing the current bid and one for the previous bid
* <p>
* option a: return BidFieldInfo with the bid set to 0 and member to null
* option b: check if field matches roughly this:
* Name: Current Bid
* Value: 160 by <@212303885988134914>
* if it does set the BidFieldInfo value to 160, and attempt ot get the user from the <@ string,
* set member to the member belonging to that user id or null if we can't find it
* option c: Find the field with the name Current Bid and proceed with that field as if it was option b
* @param messageEmbed Embed to find the fields for
* @param event Event that we need to respond to for errors
* @return BidFieldInfo or null if there was an error
*/
private BidFieldInfo getPreviousBid(MessageEmbed messageEmbed, SelectMenuInteractionEvent event) {
List<MessageEmbed.Field> fields = messageEmbed.getFields();
if (fields.size() > 2) {
event.replyEmbeds(Util.genericErrorEmbed("Error", "This auction embed has the wrong number of fields"))
.setEphemeral(true).queue();
return null;
}
if (fields.isEmpty())
return new BidFieldInfo(0, null);
MessageEmbed.Field field = fields.get(0);
if (field.getName() == null) {
event.replyEmbeds(Util.genericErrorEmbed("Error", "Found field with no name"))
.setEphemeral(true).queue();
return null;
}
if (!field.getName().equals("Current Bid")) {
if (fields.size() != 2) {
event.replyEmbeds(Util.genericErrorEmbed("Error", "Found one field and it's not the right one"))
.setEphemeral(true).queue();
return null;
}
field = fields.get(1);
if (field.getName() == null || !field.getName().equals("Current Bid")) {
event.replyEmbeds(Util.genericErrorEmbed("Error", "Unable to find current bid but it should be there"))
.setEphemeral(true).queue();
return null;
}
}
String value1 = field.getValue();
if (value1 == null) {
event.replyEmbeds(Util.genericErrorEmbed("Error", "Received default input"))
.setEphemeral(true).queue();
return null;
}
String[] s = value1.split(" ");
if (s.length < 3) {
event.replyEmbeds(Util.genericErrorEmbed("Error", "Field has incorrect input"))
.setEphemeral(true).queue();
return null;
}
int bid;
try {
bid = Integer.parseInt(s[0].substring(1));
} catch (NumberFormatException e) {
event.replyEmbeds(Util.genericErrorEmbed("Error", "Field had the wrong value"))
.setEphemeral(true).queue();
return null;
}
String substring = s[2].substring(2, s[2].length() - 1);
try {
long l = Long.parseLong(substring);
return new BidFieldInfo(bid, l);
} catch (NumberFormatException e) {
return new BidFieldInfo(bid, null);
}
}
private MessageEmbed getMessageEmbed(List<MessageEmbed> embeds, SelectMenuInteractionEvent event) {
if (embeds.isEmpty()) {
event.replyEmbeds(Util.genericErrorEmbed("Error", "Input came from message with no embeds"))
.setEphemeral(true).queue();
return null;
}
return embeds.get(0);
}
@Override
public SelectMenu getSelectMenu(SelectOption... selectOptions) {
SelectMenu.Builder builder = SelectMenu.create(getSelectMenuId());
if (selectOptions != null)
builder.addOptions(selectOptions);
return builder.build();
}
}
record BidFieldInfo(int bid, Long member) {}