diff --git a/pom.xml b/pom.xml index 1265506..dcccd98 100644 --- a/pom.xml +++ b/pom.xml @@ -92,6 +92,14 @@ compile + + + com.google.guava + guava + 32.0.0-jre + compile + + org.springframework.boot diff --git a/src/main/java/me/braydon/license/service/DiscordService.java b/src/main/java/me/braydon/license/service/DiscordService.java index 611a0f5..b69104e 100644 --- a/src/main/java/me/braydon/license/service/DiscordService.java +++ b/src/main/java/me/braydon/license/service/DiscordService.java @@ -1,5 +1,7 @@ package me.braydon.license.service; +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; import jakarta.annotation.Nonnull; import jakarta.annotation.PostConstruct; import lombok.Getter; @@ -18,11 +20,14 @@ import net.dv8tion.jda.api.entities.Activity; import net.dv8tion.jda.api.entities.MessageEmbed; import net.dv8tion.jda.api.entities.User; import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; +import net.dv8tion.jda.api.entities.emoji.Emoji; import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; +import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent; import net.dv8tion.jda.api.exceptions.ErrorResponseException; import net.dv8tion.jda.api.hooks.ListenerAdapter; import net.dv8tion.jda.api.interactions.commands.OptionType; import net.dv8tion.jda.api.interactions.commands.build.Commands; +import net.dv8tion.jda.api.interactions.components.buttons.Button; import net.dv8tion.jda.api.requests.ErrorResponse; import net.dv8tion.jda.api.requests.GatewayIntent; import org.mindrot.jbcrypt.BCrypt; @@ -32,8 +37,10 @@ import org.springframework.boot.info.BuildProperties; import org.springframework.stereotype.Service; import java.awt.*; +import java.util.HashSet; import java.util.Objects; import java.util.Optional; +import java.util.concurrent.TimeUnit; /** * @author Braydon @@ -41,6 +48,9 @@ import java.util.Optional; @Service @Slf4j(topic = "Discord") public final class DiscordService { + private static final String CLEAR_IPS_BUTTON_ID = "clearIps"; + private static final String CLEAR_HWIDS_BUTTON_ID = "clearHwids"; + /** * The {@link LicenseRepository} to use. */ @@ -116,10 +126,22 @@ public final class DiscordService { */ private JDA jda; + /** + * Cached licenses for messages. + *

+ * When a license is looked up by it's owner, the + * response message is cached (key is the message snowflake) + * for 5 minutes. This is so we're able to get the message + * an action was performed on, as well as action timeouts. + *

+ */ + private final Cache cachedLicenses = CacheBuilder.newBuilder() + .expireAfterWrite(5L, TimeUnit.MINUTES) + .build(); + @Autowired public DiscordService(@NonNull LicenseRepository licenseRepository, @NonNull BuildProperties buildProperties) { this.licenseRepository = licenseRepository; - ; this.applicationVersion = buildProperties.getVersion(); } @@ -253,51 +275,109 @@ public final class DiscordService { if (event.getName().equals("license")) { String key = Objects.requireNonNull(event.getOption("key")).getAsString(); String product = Objects.requireNonNull(event.getOption("product")).getAsString(); - event.deferReply().queue(); // Send thinking... + event.deferReply(true).queue(); // Send thinking... // License lookup - Optional optionalLicense = licenseRepository.getLicense(BCrypt.hashpw(key, licensesSalt), product); - if (optionalLicense.isEmpty() // License not found or owned by someone else - || (!optionalLicense.get().isOwner(user.getIdLong()))) { + try { + Optional optionalLicense = licenseRepository.getLicense(BCrypt.hashpw(key, licensesSalt), product); + if (optionalLicense.isEmpty() // License not found or owned by someone else + || (!optionalLicense.get().isOwner(user.getIdLong()))) { + event.getHook().sendMessageEmbeds(buildEmbed(new EmbedBuilder() + .setColor(Color.RED) + .setTitle("License not found") + .setDescription("Could not locate the license you were looking for") + )).queue(); // Send the error message + return; + } + License license = optionalLicense.get(); // The found license + String obfuscateKey = MiscUtils.obfuscateKey(key); // Obfuscate the key + long expires = license.isPermanent() ? -1L : license.getExpires().getTime() / 1000L; + long lastUsed = license.getLastUsed() == null ? -1L : license.getExpires().getTime() / 1000L; + event.getHook().sendMessageEmbeds(buildEmbed(new EmbedBuilder() + .setColor(Color.BLUE) + .setTitle("Your License") + .addField("License", "`" + obfuscateKey + "`", true) + .addField("Product", license.getProduct(), true) + .addField("Description", license.getDescription(), true) + .addField("Expiration", + expires == -1L ? "Never" : "", + true + ) + .addField("Uses", String.valueOf(license.getUses()), true) + .addField("Last Used", + lastUsed == -1L ? "Never" : "", + true + ) + .addField("IPs", + license.getIps().size() + "/" + license.getIpLimit(), + true + ) + .addField("HWIDs", + license.getHwids().size() + "/" + license.getHwidLimit(), + true + ) + .addField("Created", + "", + true + ) + )).addActionRow( // Buttons + Button.danger(CLEAR_IPS_BUTTON_ID, "Clear IPs") + .withEmoji(Emoji.fromUnicode("🗑️")), + Button.danger(CLEAR_HWIDS_BUTTON_ID, "Clear HWIDs") + .withEmoji(Emoji.fromUnicode("🗑️")) + ).queue(message -> cachedLicenses.put(message.getIdLong(), license)); // Cache the license for the message + } catch (Exception ex) { event.getHook().sendMessageEmbeds(buildEmbed(new EmbedBuilder() .setColor(Color.RED) - .setTitle("License not found") - .setDescription("Could not locate the license you were looking for") + .setTitle("Lookup Failed") + .setDescription("More information has been logged") + )).queue(); // Send the error message + ex.printStackTrace(); + } + } + } + + @Override + public void onButtonInteraction(@NonNull ButtonInteractionEvent event) { + User user = event.getUser(); // The user who clicked the button + String componentId = event.getComponentId(); // The button id + + // License Actions + boolean clearIps = componentId.equals(CLEAR_IPS_BUTTON_ID); + boolean clearHwids = componentId.equals(CLEAR_HWIDS_BUTTON_ID); + if (clearIps || clearHwids) { + event.deferReply(true).queue(); // Send thinking... + License license = cachedLicenses.getIfPresent(event.getMessageIdLong()); // Get the cached license + if (license == null || (!license.isOwner(user.getIdLong()))) { // License not found or owned by someone else + event.getHook().sendMessageEmbeds(buildEmbed(new EmbedBuilder() + .setColor(Color.RED) + .setTitle("License Action Failed") + .setDescription("The license couldn't be found or the action timed out") )).queue(); // Send the error message return; } - License license = optionalLicense.get(); // The found license - String obfuscateKey = MiscUtils.obfuscateKey(key); // Obfuscate the key - long expires = license.isPermanent() ? -1L : license.getExpires().getTime() / 1000L; - long lastUsed = license.getLastUsed() == null ? -1L : license.getExpires().getTime() / 1000L; - event.getHook().sendMessageEmbeds(buildEmbed(new EmbedBuilder() - .setColor(Color.BLUE) - .setTitle("Your License") - .addField("License", "`" + obfuscateKey + "`", true) - .addField("Product", license.getProduct(), true) - .addField("Description", license.getDescription(), true) - .addField("Expiration", - expires == -1L ? "Never" : "", - true - ) - .addField("Uses", String.valueOf(license.getUses()), true) - .addField("Last Used", - lastUsed == -1L ? "Never" : "", - true - ) - .addField("IPs", - license.getIps().size() + "/" + license.getIpLimit(), - true - ) - .addField("HWIDs", - license.getHwids().size() + "/" + license.getHwidLimit(), - true - ) - .addField("Created", - "", - true - ) - )).queue(); + try { + // Clear IPs + if (clearIps) { + license.setIps(new HashSet<>()); + } + // Clear HWIDs + if (clearHwids) { + license.setHwids(new HashSet<>()); + } + licenseRepository.save(license); // Save the license + event.getHook().sendMessageEmbeds(buildEmbed(new EmbedBuilder() + .setColor(Color.GREEN) + .setTitle("Cleared " + (clearIps ? "IP" : "HWID") + "s") + )).queue(); // Inform action success + } catch (Exception ex) { + event.getHook().sendMessageEmbeds(buildEmbed(new EmbedBuilder() + .setColor(Color.RED) + .setTitle("License Action Failed") + .setDescription("More information has been logged") + )).queue(); // Send the error message + ex.printStackTrace(); + } } } }