From ed42940e293b3b649839c50b0af194dd5dded2d0 Mon Sep 17 00:00:00 2001 From: Sheldan <5037282+Sheldan@users.noreply.github.com> Date: Mon, 27 Jan 2025 01:31:56 +0100 Subject: [PATCH] [AB-xxx] adding ability to track emotes used in reactions adding ability to have confirmations for slash commands --- .../MinesButtonClickedListener.java | 2 +- .../moderation/command/DecayAllWarnings.java | 19 -- .../moderation/command/DecayWarnings.java | 24 --- .../moderation/service/PurgeServiceBean.java | 4 +- .../emote/command/DeleteTrackedEmote.java | 54 +++++- .../emote/command/DeletedEmoteStats.java | 98 +++++++++- .../emote/command/DisableEmoteTracking.java | 56 +++++- .../statistic/emote/command/EmoteStat.java | 101 ++++++++++- .../statistic/emote/command/EmoteStats.java | 93 +++++++++- .../emote/command/ExportEmoteStats.java | 11 +- .../emote/command/ExternalEmoteStats.java | 99 +++++++++- .../emote/command/PurgeEmoteStats.java | 65 ++++++- .../emote/command/ResetEmoteStats.java | 35 +++- .../command/ShowExternalTrackedEmote.java | 57 +++++- .../emote/command/ShowTrackedEmotes.java | 96 +++++++++- .../emote/command/SyncTrackedEmotes.java | 31 +++- .../statistic/emote/command/TrackEmote.java | 70 +++++++- .../parameter/UsedEmoteTypeParameter.java | 18 ++ ...arameterSlashCommandParameterProvider.java | 20 +++ ...kedEmoteSlashCommandParameterProvider.java | 20 +++ .../statistic/emote/job/EmoteCleanupJob.java | 35 ++++ .../emote/listener/EmoteTrackingListener.java | 3 +- .../EmoteTrackingReactionAddedListener.java | 65 +++++++ .../emote/repository/UsedEmoteRepository.java | 49 ++++- .../service/RunTimeReactionEmotesService.java | 41 +++++ .../TrackedEmoteRuntimeServiceBean.java | 28 ++- .../service/TrackedEmoteServiceBean.java | 26 +-- .../emote/service/UsedEmoteServiceBean.java | 17 +- .../UsedEmoteManagementServiceBean.java | 45 +++-- .../migrations/1.5.58/collection.xml | 7 + .../migrations/1.5.58/seedData/data.xml | 6 + .../seedData/emote_statistic_cleanup_job.xml | 15 ++ .../migrations/1.5.58/tables/tables.xml | 6 + .../migrations/1.5.58/tables/used_emote.xml | 16 ++ .../migrations/statistic-changeLog.xml | 1 + .../resources/statistic-config.properties | 6 +- .../emote/command/DeletedEmoteStatsTest.java | 9 +- .../emote/command/EmoteStatsTest.java | 9 +- .../emote/command/ExternalEmoteStatsTest.java | 8 +- .../TrackedEmoteRuntimeServiceBeanTest.java | 19 +- .../service/TrackedEmoteServiceBeanTest.java | 54 +++--- .../service/UsedEmoteServiceBeanTest.java | 13 +- .../UsedEmoteManagementServiceBeanTest.java | 16 +- .../config/StatisticSlashCommandNames.java | 6 + .../config/EmoteTrackingFeatureConfig.java | 5 +- .../emote/config/EmoteTrackingMode.java | 6 +- .../emote/model/PersistingEmote.java | 2 + .../emote/model/database/UsedEmote.java | 1 + .../emote/model/database/UsedEmoteType.java | 6 + .../model/database/embed/UsedEmoteDay.java | 7 + .../service/TrackedEmoteRuntimeService.java | 19 +- .../emote/service/TrackedEmoteService.java | 9 +- .../emote/service/UsedEmoteService.java | 14 +- .../UsedEmoteManagementService.java | 24 ++- .../core/commands/utility/SetEmote.java | 2 +- .../interaction/InteractionServiceBean.java | 11 +- .../job/ConfirmationCleanupJob.java | 25 ++- .../interaction/slash/DriedSlashCommand.java | 13 ++ .../slash/SlashCommandListenerBean.java | 169 ++++++++++++++++-- .../slash/SlashCommandServiceBean.java | 22 +++ ...ommandConfirmationGivenButtonListener.java | 50 ++++++ .../SlashCommandParameterServiceBean.java | 9 +- .../SlashCommandConfirmationPayload.java | 17 ++ .../core/interaction/InteractionService.java | 3 +- .../slash/SlashCommandService.java | 4 + .../SlashCommandParameterService.java | 5 +- 66 files changed, 1630 insertions(+), 266 deletions(-) create mode 100644 abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/command/parameter/UsedEmoteTypeParameter.java create mode 100644 abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/command/parameter/handler/TrackEmoteParameterSlashCommandParameterProvider.java create mode 100644 abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/command/parameter/handler/TrackedEmoteSlashCommandParameterProvider.java create mode 100644 abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/job/EmoteCleanupJob.java create mode 100644 abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/listener/EmoteTrackingReactionAddedListener.java create mode 100644 abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/service/RunTimeReactionEmotesService.java create mode 100644 abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/resources/migrations/1.5.58/collection.xml create mode 100644 abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/resources/migrations/1.5.58/seedData/data.xml create mode 100644 abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/resources/migrations/1.5.58/seedData/emote_statistic_cleanup_job.xml create mode 100644 abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/resources/migrations/1.5.58/tables/tables.xml create mode 100644 abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/resources/migrations/1.5.58/tables/used_emote.xml create mode 100644 abstracto-application/abstracto-modules/statistic/statistic-int/src/main/java/dev/sheldan/abstracto/statistic/config/StatisticSlashCommandNames.java create mode 100644 abstracto-application/abstracto-modules/statistic/statistic-int/src/main/java/dev/sheldan/abstracto/statistic/emote/model/database/UsedEmoteType.java create mode 100644 abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/interaction/slash/DriedSlashCommand.java create mode 100644 abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/interaction/slash/listener/SlashCommandConfirmationGivenButtonListener.java create mode 100644 abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/interaction/slash/payload/SlashCommandConfirmationPayload.java diff --git a/abstracto-application/abstracto-modules/entertainment/entertainment-impl/src/main/java/dev/sheldan/abstracto/entertainment/listener/interaction/MinesButtonClickedListener.java b/abstracto-application/abstracto-modules/entertainment/entertainment-impl/src/main/java/dev/sheldan/abstracto/entertainment/listener/interaction/MinesButtonClickedListener.java index b814265f4..0722f11be 100644 --- a/abstracto-application/abstracto-modules/entertainment/entertainment-impl/src/main/java/dev/sheldan/abstracto/entertainment/listener/interaction/MinesButtonClickedListener.java +++ b/abstracto-application/abstracto-modules/entertainment/entertainment-impl/src/main/java/dev/sheldan/abstracto/entertainment/listener/interaction/MinesButtonClickedListener.java @@ -55,7 +55,7 @@ public class MinesButtonClickedListener implements ButtonClickedListener { gameService.uncoverBoard(mineBoard); } MessageToSend messageToSend = templateService.renderEmbedTemplate(Mines.MINE_BOARD_TEMPLATE_KEY, mineBoard, model.getServerId()); - interactionService.editOriginal(messageToSend, model.getEvent().getHook()).thenAccept(message -> { + interactionService.replaceOriginal(messageToSend, model.getEvent().getHook()).thenAccept(message -> { gameService.updateMineBoard(mineBoard); log.info("Updated original mineboard for board {}.", mineBoard.getBoardId()); }); diff --git a/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/command/DecayAllWarnings.java b/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/command/DecayAllWarnings.java index cd934b046..50f5b2458 100644 --- a/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/command/DecayAllWarnings.java +++ b/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/command/DecayAllWarnings.java @@ -4,7 +4,6 @@ import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand import dev.sheldan.abstracto.core.command.config.CommandConfiguration; import dev.sheldan.abstracto.core.command.config.HelpInfo; import dev.sheldan.abstracto.core.command.config.Parameter; -import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig; import dev.sheldan.abstracto.core.command.execution.CommandContext; import dev.sheldan.abstracto.core.command.execution.CommandResult; import dev.sheldan.abstracto.core.config.FeatureDefinition; @@ -12,10 +11,8 @@ import dev.sheldan.abstracto.core.interaction.InteractionService; import dev.sheldan.abstracto.core.models.database.AServer; import dev.sheldan.abstracto.core.service.management.ServerManagementService; import dev.sheldan.abstracto.moderation.config.ModerationModuleDefinition; -import dev.sheldan.abstracto.moderation.config.ModerationSlashCommandNames; import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition; import dev.sheldan.abstracto.moderation.service.WarnService; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -45,14 +42,6 @@ public class DecayAllWarnings extends AbstractConditionableCommand { .thenApply(aVoid -> CommandResult.fromSuccess()); } - @Override - public CompletableFuture executeSlash(SlashCommandInteractionEvent event) { - AServer server = serverManagementService.loadServer(event.getGuild()); - return warnService.decayAllWarningsForServer(server) - .thenCompose(unused -> interactionService.replyEmbed(DECAY_ALL_WARNINGS_RESPONSE, event)) - .thenApply(aVoid -> CommandResult.fromSuccess()); - } - @Override public CommandConfiguration getConfiguration() { List parameters = new ArrayList<>(); @@ -61,19 +50,11 @@ public class DecayAllWarnings extends AbstractConditionableCommand { .templated(true) .build(); - SlashCommandConfig slashCommandConfig = SlashCommandConfig - .builder() - .enabled(true) - .rootCommandName(ModerationSlashCommandNames.WARN_DECAY) - .commandName(DECAY_ALL_WARNINGS_COMMAND) - .build(); - return CommandConfiguration.builder() .name(DECAY_ALL_WARNINGS_COMMAND) .module(ModerationModuleDefinition.MODERATION) .templated(true) .async(true) - .slashCommandConfig(slashCommandConfig) .requiresConfirmation(true) .supportsEmbedException(true) .causesReaction(true) diff --git a/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/command/DecayWarnings.java b/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/command/DecayWarnings.java index a22054487..03d6b1578 100644 --- a/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/command/DecayWarnings.java +++ b/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/command/DecayWarnings.java @@ -4,18 +4,14 @@ import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand import dev.sheldan.abstracto.core.command.config.CommandConfiguration; import dev.sheldan.abstracto.core.command.config.HelpInfo; import dev.sheldan.abstracto.core.command.config.Parameter; -import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig; import dev.sheldan.abstracto.core.command.execution.CommandContext; import dev.sheldan.abstracto.core.command.execution.CommandResult; import dev.sheldan.abstracto.core.config.FeatureDefinition; -import dev.sheldan.abstracto.core.interaction.InteractionService; import dev.sheldan.abstracto.core.models.database.AServer; import dev.sheldan.abstracto.core.service.management.ServerManagementService; import dev.sheldan.abstracto.moderation.config.ModerationModuleDefinition; -import dev.sheldan.abstracto.moderation.config.ModerationSlashCommandNames; import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition; import dev.sheldan.abstracto.moderation.service.WarnService; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -27,7 +23,6 @@ import java.util.concurrent.CompletableFuture; public class DecayWarnings extends AbstractConditionableCommand { private static final String DECAY_WARNINGS_COMMAND = "decayWarnings"; - private static final String DECAY_WARNINGS_RESPONSE = "decayWarnings_response"; @Autowired private WarnService warnService; @@ -35,9 +30,6 @@ public class DecayWarnings extends AbstractConditionableCommand { @Autowired private ServerManagementService serverManagementService; - @Autowired - private InteractionService interactionService; - @Override public CompletableFuture executeAsync(CommandContext commandContext) { AServer server = serverManagementService.loadServer(commandContext.getGuild()); @@ -45,14 +37,6 @@ public class DecayWarnings extends AbstractConditionableCommand { .thenApply(aVoid -> CommandResult.fromSuccess()); } - @Override - public CompletableFuture executeSlash(SlashCommandInteractionEvent event) { - AServer server = serverManagementService.loadServer(event.getGuild()); - return warnService.decayWarningsForServer(server) - .thenCompose(unused -> interactionService.replyEmbed(DECAY_WARNINGS_RESPONSE, event)) - .thenApply(aVoid -> CommandResult.fromSuccess()); - } - @Override public CommandConfiguration getConfiguration() { List parameters = new ArrayList<>(); @@ -61,18 +45,10 @@ public class DecayWarnings extends AbstractConditionableCommand { .templated(true) .build(); - SlashCommandConfig slashCommandConfig = SlashCommandConfig - .builder() - .enabled(true) - .rootCommandName(ModerationSlashCommandNames.WARN_DECAY) - .commandName(DECAY_WARNINGS_COMMAND) - .build(); - return CommandConfiguration.builder() .name(DECAY_WARNINGS_COMMAND) .module(ModerationModuleDefinition.MODERATION) .templated(true) - .slashCommandConfig(slashCommandConfig) .requiresConfirmation(true) .async(true) .supportsEmbedException(true) diff --git a/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/service/PurgeServiceBean.java b/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/service/PurgeServiceBean.java index b1f2ec69a..6deaa837d 100644 --- a/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/service/PurgeServiceBean.java +++ b/abstracto-application/abstracto-modules/moderation/moderation-impl/src/main/java/dev/sheldan/abstracto/moderation/service/PurgeServiceBean.java @@ -217,7 +217,7 @@ public class PurgeServiceBean implements PurgeService { } log.debug("Setting status for {} out of {}", currentCount, totalCount); MessageToSend finalUpdateMessage = getStatusMessageToSend(totalCount, channel.getGuild().getIdLong(), currentCount); - interactionService.editOriginal(finalUpdateMessage, interactionHook); + interactionService.replaceOriginal(finalUpdateMessage, interactionHook); }; } @@ -244,7 +244,7 @@ public class PurgeServiceBean implements PurgeService { CompletableFuture historyFuture = channelService.getHistoryOfChannel(channel, startId, toDeleteInThisIteration); MessageToSend statusMessageToSend = getStatusMessageToSend(totalCount, channel.getGuild().getIdLong(), 0); - CompletableFuture statusMessageFuture = interactionService.editOriginal(statusMessageToSend, interactionHook); + CompletableFuture statusMessageFuture = interactionService.replaceOriginal(statusMessageToSend, interactionHook); CompletableFuture deletionFuture = new CompletableFuture<>(); CompletableFuture retrievalFuture = CompletableFuture.allOf(historyFuture, statusMessageFuture); diff --git a/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/command/DeleteTrackedEmote.java b/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/command/DeleteTrackedEmote.java index 46aa328f6..007ab7f1d 100644 --- a/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/command/DeleteTrackedEmote.java +++ b/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/command/DeleteTrackedEmote.java @@ -7,11 +7,21 @@ import dev.sheldan.abstracto.core.command.config.Parameter; import dev.sheldan.abstracto.core.command.execution.CommandContext; import dev.sheldan.abstracto.core.command.execution.CommandResult; import dev.sheldan.abstracto.core.config.FeatureDefinition; +import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig; +import dev.sheldan.abstracto.core.interaction.slash.SlashCommandService; +import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParameterService; +import dev.sheldan.abstracto.core.models.ServerSpecificId; import dev.sheldan.abstracto.statistic.config.StatisticFeatureDefinition; +import dev.sheldan.abstracto.statistic.config.StatisticSlashCommandNames; import dev.sheldan.abstracto.statistic.emote.config.EmoteTrackingModuleDefinition; +import dev.sheldan.abstracto.statistic.emote.exception.TrackedEmoteNotFoundException; import dev.sheldan.abstracto.statistic.emote.model.database.TrackedEmote; import dev.sheldan.abstracto.statistic.emote.service.TrackedEmoteService; import dev.sheldan.abstracto.statistic.emote.service.management.TrackedEmoteManagementService; +import java.util.concurrent.CompletableFuture; +import net.dv8tion.jda.api.entities.emoji.CustomEmoji; +import net.dv8tion.jda.api.entities.emoji.Emoji; +import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -30,6 +40,16 @@ public class DeleteTrackedEmote extends AbstractConditionableCommand { @Autowired private TrackedEmoteService trackedEmoteService; + @Autowired + private SlashCommandParameterService slashCommandParameterService; + + @Autowired + private SlashCommandService slashCommandService; + + private static final String DELETE_TRACKED_EMOTE_TRACKED_EMOTE = "trackedEmote"; + private static final String DELETE_TRACKED_EMOTE_COMMAND_NAME = "deleteTrackedEmote"; + private static final String DELETE_TRACKED_EMOTE_RESPONSE = "deleteTrackedEmote_response"; + @Override public CommandResult execute(CommandContext commandContext) { List parameters = commandContext.getParameters().getParameters(); @@ -40,24 +60,52 @@ public class DeleteTrackedEmote extends AbstractConditionableCommand { return CommandResult.fromSuccess(); } + @Override + public CompletableFuture executeSlash(SlashCommandInteractionEvent event) { + String emote = slashCommandParameterService.getCommandOption(DELETE_TRACKED_EMOTE_TRACKED_EMOTE, event, String.class); + Emoji emoji = slashCommandParameterService.loadEmoteFromString(emote, event.getGuild()); + if(!(emoji instanceof CustomEmoji)) { + throw new TrackedEmoteNotFoundException(); + } + Long emoteId = ((CustomEmoji) emoji).getIdLong(); + ServerSpecificId serverEmoteId = new ServerSpecificId(event.getGuild().getIdLong(), emoteId); + TrackedEmote trackedEmote = trackedEmoteManagementService.loadByTrackedEmoteServer(serverEmoteId); + trackedEmoteService.deleteTrackedEmote(trackedEmote); + return slashCommandService.completeConfirmableCommand(event, DELETE_TRACKED_EMOTE_RESPONSE); + } + @Override public CommandConfiguration getConfiguration() { List parameters = new ArrayList<>(); Parameter trackedEmoteParameter = Parameter .builder() - .name("trackedEmote") + .name(DELETE_TRACKED_EMOTE_TRACKED_EMOTE) .templated(true) .type(TrackedEmote.class) .build(); parameters.add(trackedEmoteParameter); - HelpInfo helpInfo = HelpInfo.builder().templated(true).build(); + + HelpInfo helpInfo = HelpInfo + .builder() + .templated(true) + .build(); + + SlashCommandConfig slashCommandConfig = SlashCommandConfig + .builder() + .enabled(true) + .rootCommandName(StatisticSlashCommandNames.STATISTIC_INTERNAL) + .groupName("manage") + .commandName("deletetrackedemote") + .build(); + return CommandConfiguration.builder() - .name("deleteTrackedEmote") + .name(DELETE_TRACKED_EMOTE_COMMAND_NAME) .module(EmoteTrackingModuleDefinition.EMOTE_TRACKING) .templated(true) .supportsEmbedException(true) .causesReaction(true) .messageCommandOnly(true) + .slashCommandConfig(slashCommandConfig) .requiresConfirmation(true) .parameters(parameters) .help(helpInfo) diff --git a/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/command/DeletedEmoteStats.java b/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/command/DeletedEmoteStats.java index 869e692ff..ecf234007 100644 --- a/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/command/DeletedEmoteStats.java +++ b/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/command/DeletedEmoteStats.java @@ -7,16 +7,25 @@ import dev.sheldan.abstracto.core.command.config.Parameter; import dev.sheldan.abstracto.core.command.execution.CommandContext; import dev.sheldan.abstracto.core.command.execution.CommandResult; import dev.sheldan.abstracto.core.config.FeatureDefinition; +import dev.sheldan.abstracto.core.interaction.InteractionService; +import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig; +import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParameterService; import dev.sheldan.abstracto.core.models.database.AServer; import dev.sheldan.abstracto.core.service.ChannelService; import dev.sheldan.abstracto.core.service.management.ServerManagementService; import dev.sheldan.abstracto.core.utils.FutureUtils; +import dev.sheldan.abstracto.core.utils.ParseUtils; import dev.sheldan.abstracto.statistic.config.StatisticFeatureDefinition; +import dev.sheldan.abstracto.statistic.config.StatisticSlashCommandNames; +import dev.sheldan.abstracto.statistic.emote.command.parameter.UsedEmoteTypeParameter; import dev.sheldan.abstracto.statistic.emote.config.EmoteTrackingModuleDefinition; import dev.sheldan.abstracto.statistic.emote.model.EmoteStatsModel; import dev.sheldan.abstracto.statistic.emote.service.UsedEmoteService; +import java.util.Arrays; +import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; import net.dv8tion.jda.api.entities.Message; +import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -34,6 +43,7 @@ import java.util.concurrent.CompletableFuture; @Slf4j public class DeletedEmoteStats extends AbstractConditionableCommand { + public static final String DELETED_EMOTE_STATS_COMMAND_NAME = "deletedEmoteStats"; @Autowired private UsedEmoteService usedEmoteService; @@ -43,8 +53,16 @@ public class DeletedEmoteStats extends AbstractConditionableCommand { @Autowired private ServerManagementService serverManagementService; + @Autowired + private SlashCommandParameterService slashCommandParameterService; + + @Autowired + private InteractionService interactionService; + public static final String EMOTE_STATS_STATIC_DELETED_RESPONSE = "deletedEmoteStats_static_response"; public static final String EMOTE_STATS_ANIMATED_DELETED_RESPONSE = "deletedEmoteStats_animated_response"; + private static final String DELETED_EMOTE_STATS_PERIOD = "period"; + private static final String DELETED_EMOTE_STATS_USED_EMOTE_TYPE = "type"; @Override public CompletableFuture executeAsync(CommandContext commandContext) { @@ -57,7 +75,7 @@ public class DeletedEmoteStats extends AbstractConditionableCommand { statsSince = Instant.now().minus(duration); } AServer server = serverManagementService.loadServer(commandContext.getGuild()); - EmoteStatsModel emoteStatsModel = usedEmoteService.getDeletedEmoteStatsForServerSince(server, statsSince); + EmoteStatsModel emoteStatsModel = usedEmoteService.getDeletedEmoteStatsForServerSince(server, statsSince, null); List> messagePromises = new ArrayList<>(); // only show the embed, if there are static emotes to show if(!emoteStatsModel.getStaticEmotes().isEmpty()) { @@ -79,23 +97,95 @@ public class DeletedEmoteStats extends AbstractConditionableCommand { .thenApply(unused -> CommandResult.fromIgnored()); } + @Override + public CompletableFuture executeSlash(SlashCommandInteractionEvent event) { + UsedEmoteTypeParameter typeEnum; + if(slashCommandParameterService.hasCommandOption(DELETED_EMOTE_STATS_USED_EMOTE_TYPE, event)) { + String type = slashCommandParameterService.getCommandOption(DELETED_EMOTE_STATS_USED_EMOTE_TYPE, event, String.class); + typeEnum = UsedEmoteTypeParameter.valueOf(type); + } else { + typeEnum = null; + } + Instant startTime; + if(slashCommandParameterService.hasCommandOption(DELETED_EMOTE_STATS_PERIOD, event)) { + String durationString = slashCommandParameterService.getCommandOption(DELETED_EMOTE_STATS_PERIOD, event, Duration.class, String.class); + Duration durationSince = ParseUtils.parseDuration(durationString); + startTime = Instant.now().minus(durationSince); + } else { + startTime = Instant.EPOCH; + } + AServer server = serverManagementService.loadServer(event.getGuild()); + EmoteStatsModel emoteStatsModel = usedEmoteService.getDeletedEmoteStatsForServerSince(server, startTime, UsedEmoteTypeParameter.convertToUsedEmoteType(typeEnum)); + List> messagePromises = new ArrayList<>(); + return event.deferReply().submit().thenCompose(interactionHook -> { + // only show embed if static emote stats are available + if(!emoteStatsModel.getStaticEmotes().isEmpty()) { + log.debug("Deleted emote stats has {} static emotes since {}.", emoteStatsModel.getStaticEmotes().size(), startTime); + messagePromises.addAll(interactionService.sendMessageToInteraction(EMOTE_STATS_STATIC_DELETED_RESPONSE, emoteStatsModel, interactionHook)); + } + // only show embed if animated emote stats are available + if(!emoteStatsModel.getAnimatedEmotes().isEmpty()) { + log.debug("Deleted emote stats has {} animated emotes since {}.", emoteStatsModel.getAnimatedEmotes(), startTime); + messagePromises.addAll(interactionService.sendMessageToInteraction(EMOTE_STATS_ANIMATED_DELETED_RESPONSE, emoteStatsModel, interactionHook)); + } + // show an embed if no emote stats are available indicating so + if(!emoteStatsModel.areStatsAvailable()) { + log.info("No delete emote stats available for guild {} since {}.", event.getGuild().getIdLong(), startTime); + messagePromises.addAll(interactionService.sendMessageToInteraction(EmoteStats.EMOTE_STATS_NO_STATS_AVAILABLE, emoteStatsModel, interactionHook)); + } + return FutureUtils.toSingleFutureGeneric(messagePromises) + .thenApply(unused -> CommandResult.fromIgnored()); + }); + } + @Override public CommandConfiguration getConfiguration() { List parameters = new ArrayList<>(); Parameter periodParameter = Parameter .builder() - .name("period") + .name(DELETED_EMOTE_STATS_PERIOD) .templated(true) .optional(true) .type(Duration.class) .build(); parameters.add(periodParameter); - HelpInfo helpInfo = HelpInfo.builder().templated(true).build(); + + List emoteTypes = Arrays + .stream(UsedEmoteTypeParameter.values()) + .map(Enum::name) + .collect(Collectors.toList()); + + Parameter typeParameter = Parameter + .builder() + .name(DELETED_EMOTE_STATS_USED_EMOTE_TYPE) + .templated(true) + .slashCommandOnly(true) + .optional(true) + .choices(emoteTypes) + .type(String.class) + .build(); + + parameters.add(typeParameter); + + SlashCommandConfig slashCommandConfig = SlashCommandConfig + .builder() + .enabled(true) + .rootCommandName(StatisticSlashCommandNames.STATISTIC) + .groupName("emotestats") + .commandName("deleted") + .build(); + + HelpInfo helpInfo = HelpInfo + .builder() + .templated(true) + .build(); + return CommandConfiguration.builder() - .name("deletedEmoteStats") + .name(DELETED_EMOTE_STATS_COMMAND_NAME) .module(EmoteTrackingModuleDefinition.EMOTE_TRACKING) .templated(true) .async(true) + .slashCommandConfig(slashCommandConfig) .supportsEmbedException(true) .messageCommandOnly(true) .causesReaction(true) diff --git a/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/command/DisableEmoteTracking.java b/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/command/DisableEmoteTracking.java index 7a36e96e0..06643e9a4 100644 --- a/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/command/DisableEmoteTracking.java +++ b/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/command/DisableEmoteTracking.java @@ -7,11 +7,20 @@ import dev.sheldan.abstracto.core.command.config.Parameter; import dev.sheldan.abstracto.core.command.execution.CommandContext; import dev.sheldan.abstracto.core.command.execution.CommandResult; import dev.sheldan.abstracto.core.config.FeatureDefinition; +import dev.sheldan.abstracto.core.interaction.InteractionService; +import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig; +import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParameterService; +import dev.sheldan.abstracto.core.models.ServerSpecificId; import dev.sheldan.abstracto.statistic.config.StatisticFeatureDefinition; +import dev.sheldan.abstracto.statistic.config.StatisticSlashCommandNames; import dev.sheldan.abstracto.statistic.emote.config.EmoteTrackingModuleDefinition; import dev.sheldan.abstracto.statistic.emote.model.database.TrackedEmote; import dev.sheldan.abstracto.statistic.emote.service.TrackedEmoteService; import dev.sheldan.abstracto.statistic.emote.service.management.TrackedEmoteManagementService; +import java.util.concurrent.CompletableFuture; +import net.dv8tion.jda.api.entities.emoji.CustomEmoji; +import net.dv8tion.jda.api.entities.emoji.Emoji; +import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -30,6 +39,17 @@ public class DisableEmoteTracking extends AbstractConditionableCommand { @Autowired private TrackedEmoteManagementService trackedEmoteManagementService; + @Autowired + private SlashCommandParameterService slashCommandParameterService; + + @Autowired + private InteractionService interactionService; + + private static final String DISABLE_EMOTE_TRACKING_COMMAND_NAME = "disableEmoteTracking"; + private static final String DISABLE_EMOTE_TRACKING_TRACKED_EMOTE = "trackedEmote"; + private static final String DISABLE_EMOTE_TRACKING_RESPONSE = "disableEmoteTracking_response"; + + @Override public CommandResult execute(CommandContext commandContext) { List parameters = commandContext.getParameters().getParameters(); @@ -44,22 +64,52 @@ public class DisableEmoteTracking extends AbstractConditionableCommand { return CommandResult.fromSuccess(); } + @Override + public CompletableFuture executeSlash(SlashCommandInteractionEvent event) { + if(slashCommandParameterService.hasCommandOption(DISABLE_EMOTE_TRACKING_TRACKED_EMOTE, event)) { + String emote = slashCommandParameterService.getCommandOption(DISABLE_EMOTE_TRACKING_TRACKED_EMOTE, event, String.class); + Emoji emoji = slashCommandParameterService.loadEmoteFromString(emote, event.getGuild()); + Long emoteId = ((CustomEmoji) emoji).getIdLong(); + ServerSpecificId serverEmoteId = new ServerSpecificId(event.getGuild().getIdLong(), emoteId); + TrackedEmote trackedEmote = trackedEmoteManagementService.loadByTrackedEmoteServer(serverEmoteId); + trackedEmoteManagementService.disableTrackedEmote(trackedEmote); + } else { + trackedEmoteService.disableEmoteTracking(event.getGuild()); + } + return interactionService.replyEmbed(DISABLE_EMOTE_TRACKING_RESPONSE, event) + .thenApply(interactionHook -> CommandResult.fromIgnored()); + } + @Override public CommandConfiguration getConfiguration() { List parameters = new ArrayList<>(); Parameter trackedEmoteParameter = Parameter .builder() - .name("trackedEmote") + .name(DISABLE_EMOTE_TRACKING_TRACKED_EMOTE) .templated(true) .optional(true) .type(TrackedEmote.class) .build(); parameters.add(trackedEmoteParameter); - HelpInfo helpInfo = HelpInfo.builder().templated(true).build(); + + HelpInfo helpInfo = HelpInfo + .builder() + .templated(true) + .build(); + + SlashCommandConfig slashCommandConfig = SlashCommandConfig + .builder() + .enabled(true) + .rootCommandName(StatisticSlashCommandNames.STATISTIC_INTERNAL) + .groupName("manage") + .commandName("disableemotetracking") + .build(); + return CommandConfiguration.builder() - .name("disableEmoteTracking") + .name(DISABLE_EMOTE_TRACKING_COMMAND_NAME) .module(EmoteTrackingModuleDefinition.EMOTE_TRACKING) .templated(true) + .slashCommandConfig(slashCommandConfig) .messageCommandOnly(true) .supportsEmbedException(true) .causesReaction(true) diff --git a/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/command/EmoteStat.java b/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/command/EmoteStat.java index 3536bc00a..36081a637 100644 --- a/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/command/EmoteStat.java +++ b/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/command/EmoteStat.java @@ -7,15 +7,28 @@ import dev.sheldan.abstracto.core.command.config.Parameter; import dev.sheldan.abstracto.core.command.execution.CommandContext; import dev.sheldan.abstracto.core.command.execution.CommandResult; import dev.sheldan.abstracto.core.config.FeatureDefinition; +import dev.sheldan.abstracto.core.interaction.InteractionService; +import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig; +import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParameterService; +import dev.sheldan.abstracto.core.models.ServerSpecificId; import dev.sheldan.abstracto.core.service.ChannelService; import dev.sheldan.abstracto.core.utils.FutureUtils; +import dev.sheldan.abstracto.core.utils.ParseUtils; import dev.sheldan.abstracto.statistic.config.StatisticFeatureDefinition; +import dev.sheldan.abstracto.statistic.config.StatisticSlashCommandNames; +import dev.sheldan.abstracto.statistic.emote.command.parameter.UsedEmoteTypeParameter; import dev.sheldan.abstracto.statistic.emote.config.EmoteTrackingModuleDefinition; +import dev.sheldan.abstracto.statistic.emote.exception.TrackedEmoteNotFoundException; import dev.sheldan.abstracto.statistic.emote.model.EmoteStatsResultDisplay; import dev.sheldan.abstracto.statistic.emote.model.database.TrackedEmote; import dev.sheldan.abstracto.statistic.emote.service.UsedEmoteService; import dev.sheldan.abstracto.statistic.emote.service.management.TrackedEmoteManagementService; +import java.util.Arrays; +import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; +import net.dv8tion.jda.api.entities.emoji.CustomEmoji; +import net.dv8tion.jda.api.entities.emoji.Emoji; +import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -40,7 +53,18 @@ public class EmoteStat extends AbstractConditionableCommand { @Autowired private TrackedEmoteManagementService trackedEmoteManagementService; - public static final String EMOTE_STAT_RESPONSE = "emoteStat_response"; + @Autowired + private SlashCommandParameterService slashCommandParameterService; + + @Autowired + private InteractionService interactionService; + + private static final String EMOTE_STAT_RESPONSE = "emoteStat_response"; + + private static final String EMOTE_STAT_USED_EMOTE_TYPE = "type"; + private static final String EMOTE_STAT_DURATION = "period"; + private static final String EMOTE_STAT_TRACKED_EMOTE = "trackedEmote"; + private static final String EMOTE_STAT_COMMAND_NAME = "emoteStat"; @Override public CompletableFuture executeAsync(CommandContext commandContext) { @@ -54,7 +78,7 @@ public class EmoteStat extends AbstractConditionableCommand { Duration duration = (Duration) parameters.get(1); statsSince = Instant.now().minus(duration); } - EmoteStatsResultDisplay emoteStatsModel = usedEmoteService.getEmoteStatForEmote(trackedEmote, statsSince); + EmoteStatsResultDisplay emoteStatsModel = usedEmoteService.getEmoteStatForEmote(trackedEmote, statsSince, null); if(emoteStatsModel.getResult().getAmount() == null) { return FutureUtils.toSingleFutureGeneric(channelService.sendEmbedTemplateInMessageChannel(EMOTE_STATS_NO_STATS_AVAILABLE, new Object(), commandContext.getChannel())) .thenApply(unused -> CommandResult.fromIgnored()); @@ -63,29 +87,94 @@ public class EmoteStat extends AbstractConditionableCommand { .thenApply(unused -> CommandResult.fromIgnored()); } + @Override + public CompletableFuture executeSlash(SlashCommandInteractionEvent event) { + UsedEmoteTypeParameter typeEnum; + if(slashCommandParameterService.hasCommandOption(EMOTE_STAT_USED_EMOTE_TYPE, event)) { + String type = slashCommandParameterService.getCommandOption(EMOTE_STAT_USED_EMOTE_TYPE, event, String.class); + typeEnum = UsedEmoteTypeParameter.valueOf(type); + } else { + typeEnum = null; + } + Instant startTime; + if(slashCommandParameterService.hasCommandOption(EMOTE_STAT_DURATION, event)) { + String durationString = slashCommandParameterService.getCommandOption(EMOTE_STAT_DURATION, event, Duration.class, String.class); + Duration durationSince = ParseUtils.parseDuration(durationString); + startTime = Instant.now().minus(durationSince); + } else { + startTime = Instant.EPOCH; + } + String emote = slashCommandParameterService.getCommandOption(EMOTE_STAT_TRACKED_EMOTE, event, String.class); + Emoji emoji = slashCommandParameterService.loadEmoteFromString(emote, event.getGuild()); + if(emoji instanceof CustomEmoji) { + Long emoteId = ((CustomEmoji) emoji).getIdLong(); + TrackedEmote trackedEmote = trackedEmoteManagementService.loadByTrackedEmoteServer(new ServerSpecificId(event.getGuild().getIdLong(), emoteId)); + EmoteStatsResultDisplay emoteStatsModel = usedEmoteService.getEmoteStatForEmote(trackedEmote, startTime, UsedEmoteTypeParameter.convertToUsedEmoteType(typeEnum)); + if(emoteStatsModel.getResult().getAmount() == null) { + return interactionService.replyEmbed(EMOTE_STATS_NO_STATS_AVAILABLE, new Object(), event) + .thenApply(unused -> CommandResult.fromIgnored()); + } + return interactionService.replyEmbed(EMOTE_STAT_RESPONSE, emoteStatsModel, event) + .thenApply(unused -> CommandResult.fromIgnored()); + } else { + throw new TrackedEmoteNotFoundException(); + } + } + @Override public CommandConfiguration getConfiguration() { List parameters = new ArrayList<>(); Parameter trackedEmoteParameter = Parameter .builder() - .name("trackedEmote") + .name(EMOTE_STAT_TRACKED_EMOTE) .templated(true) .type(TrackedEmote.class) .build(); parameters.add(trackedEmoteParameter); + Parameter periodParameter = Parameter .builder() - .name("period") + .name(EMOTE_STAT_DURATION) .templated(true) .optional(true) .type(Duration.class) .build(); parameters.add(periodParameter); - HelpInfo helpInfo = HelpInfo.builder().templated(true).build(); + + List emoteTypes = Arrays + .stream(UsedEmoteTypeParameter.values()) + .map(Enum::name) + .collect(Collectors.toList()); + + Parameter typeParameter = Parameter + .builder() + .name(EMOTE_STAT_USED_EMOTE_TYPE) + .templated(true) + .slashCommandOnly(true) + .optional(true) + .choices(emoteTypes) + .type(String.class) + .build(); + + parameters.add(typeParameter); + + SlashCommandConfig slashCommandConfig = SlashCommandConfig + .builder() + .enabled(true) + .rootCommandName(StatisticSlashCommandNames.STATISTIC) + .groupName("emotestats") + .commandName("singular") + .build(); + + HelpInfo helpInfo = HelpInfo + .builder() + .templated(true) + .build(); return CommandConfiguration.builder() - .name("emoteStat") + .name(EMOTE_STAT_COMMAND_NAME) .module(EmoteTrackingModuleDefinition.EMOTE_TRACKING) .templated(true) + .slashCommandConfig(slashCommandConfig) .async(true) .messageCommandOnly(true) .supportsEmbedException(true) diff --git a/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/command/EmoteStats.java b/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/command/EmoteStats.java index 0ff7d36a6..3b54cc6cc 100644 --- a/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/command/EmoteStats.java +++ b/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/command/EmoteStats.java @@ -7,16 +7,25 @@ import dev.sheldan.abstracto.core.command.config.Parameter; import dev.sheldan.abstracto.core.command.execution.CommandContext; import dev.sheldan.abstracto.core.command.execution.CommandResult; import dev.sheldan.abstracto.core.config.FeatureDefinition; +import dev.sheldan.abstracto.core.interaction.InteractionService; +import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig; +import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParameterService; import dev.sheldan.abstracto.core.models.database.AServer; import dev.sheldan.abstracto.core.service.ChannelService; import dev.sheldan.abstracto.core.service.management.ServerManagementService; import dev.sheldan.abstracto.core.utils.FutureUtils; +import dev.sheldan.abstracto.core.utils.ParseUtils; import dev.sheldan.abstracto.statistic.config.StatisticFeatureDefinition; +import dev.sheldan.abstracto.statistic.config.StatisticSlashCommandNames; +import dev.sheldan.abstracto.statistic.emote.command.parameter.UsedEmoteTypeParameter; import dev.sheldan.abstracto.statistic.emote.config.EmoteTrackingModuleDefinition; import dev.sheldan.abstracto.statistic.emote.model.EmoteStatsModel; import dev.sheldan.abstracto.statistic.emote.service.UsedEmoteService; +import java.util.Arrays; +import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; import net.dv8tion.jda.api.entities.Message; +import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -43,9 +52,18 @@ public class EmoteStats extends AbstractConditionableCommand { @Autowired private ServerManagementService serverManagementService; + @Autowired + private SlashCommandParameterService slashCommandParameterService; + + @Autowired + private InteractionService interactionService; + public static final String EMOTE_STATS_STATIC_RESPONSE = "emoteStats_static_response"; public static final String EMOTE_STATS_ANIMATED_RESPONSE = "emoteStats_animated_response"; public static final String EMOTE_STATS_NO_STATS_AVAILABLE = "emoteStats_no_stats_available"; + private static final String EMOTE_STATS_USED_EMOTE_TYPE = "type"; + private static final String EMOTE_STATS_DURATION = "period"; + private static final String EMOTE_STATS_COMMAND_NAME = "emoteStats"; @Override public CompletableFuture executeAsync(CommandContext commandContext) { @@ -58,7 +76,7 @@ public class EmoteStats extends AbstractConditionableCommand { statsSince = Instant.now().minus(duration); } AServer server = serverManagementService.loadServer(commandContext.getGuild()); - EmoteStatsModel emoteStatsModel = usedEmoteService.getActiveEmoteStatsForServerSince(server, statsSince); + EmoteStatsModel emoteStatsModel = usedEmoteService.getActiveEmoteStatsForServerSince(server, statsSince, null); List> messagePromises = new ArrayList<>(); // only show embed if static emote stats are available if(!emoteStatsModel.getStaticEmotes().isEmpty()) { @@ -80,24 +98,91 @@ public class EmoteStats extends AbstractConditionableCommand { .thenApply(unused -> CommandResult.fromIgnored()); } + @Override + public CompletableFuture executeSlash(SlashCommandInteractionEvent event) { + UsedEmoteTypeParameter typeEnum; + if(slashCommandParameterService.hasCommandOption(EMOTE_STATS_USED_EMOTE_TYPE, event)) { + String type = slashCommandParameterService.getCommandOption(EMOTE_STATS_USED_EMOTE_TYPE, event, String.class); + typeEnum = UsedEmoteTypeParameter.valueOf(type); + } else { + typeEnum = null; + } + Instant startTime; + if(slashCommandParameterService.hasCommandOption(EMOTE_STATS_DURATION, event)) { + String durationString = slashCommandParameterService.getCommandOption(EMOTE_STATS_DURATION, event, Duration.class, String.class); + Duration durationSince = ParseUtils.parseDuration(durationString); + startTime = Instant.now().minus(durationSince); + } else { + startTime = Instant.EPOCH; + } + AServer server = serverManagementService.loadServer(event.getGuild()); + EmoteStatsModel emoteStatsModel = usedEmoteService.getActiveEmoteStatsForServerSince(server, startTime, UsedEmoteTypeParameter.convertToUsedEmoteType(typeEnum)); + List> messagePromises = new ArrayList<>(); + return event.deferReply().submit().thenCompose(interactionHook -> { + // only show embed if static emote stats are available + if(!emoteStatsModel.getStaticEmotes().isEmpty()) { + log.debug("Emote stats has {} static emotes since {}.", emoteStatsModel.getStaticEmotes().size(), startTime); + messagePromises.addAll(interactionService.sendMessageToInteraction(EMOTE_STATS_STATIC_RESPONSE, emoteStatsModel, interactionHook)); + } + // only show embed if animated emote stats are available + if(!emoteStatsModel.getAnimatedEmotes().isEmpty()) { + log.debug("Emote stats has {} animated emotes since {}.", emoteStatsModel.getAnimatedEmotes(), startTime); + messagePromises.addAll(interactionService.sendMessageToInteraction(EMOTE_STATS_ANIMATED_RESPONSE, emoteStatsModel, interactionHook)); + } + // show an embed if no emote stats are available indicating so + if(!emoteStatsModel.areStatsAvailable()) { + log.info("No emote stats available for guild {} since {}.", event.getGuild().getIdLong(), startTime); + messagePromises.addAll(interactionService.sendMessageToInteraction(EMOTE_STATS_NO_STATS_AVAILABLE, emoteStatsModel, interactionHook)); + } + return FutureUtils.toSingleFutureGeneric(messagePromises) + .thenApply(unused -> CommandResult.fromIgnored()); + }); + } + @Override public CommandConfiguration getConfiguration() { List parameters = new ArrayList<>(); Parameter periodParameter = Parameter .builder() - .name("period") + .name(EMOTE_STATS_DURATION) .templated(true) .optional(true) .type(Duration.class) .build(); + parameters.add(periodParameter); + + List emoteTypes = Arrays + .stream(UsedEmoteTypeParameter.values()) + .map(Enum::name) + .collect(Collectors.toList()); + + Parameter typeParameter = Parameter + .builder() + .name(EMOTE_STATS_USED_EMOTE_TYPE) + .templated(true) + .slashCommandOnly(true) + .optional(true) + .choices(emoteTypes) + .type(String.class) + .build(); + + SlashCommandConfig slashCommandConfig = SlashCommandConfig + .builder() + .enabled(true) + .rootCommandName(StatisticSlashCommandNames.STATISTIC) + .groupName("emotestats") + .commandName("current") + .build(); + parameters.add(typeParameter); HelpInfo helpInfo = HelpInfo.builder().templated(true).build(); return CommandConfiguration.builder() - .name("emoteStats") + .name(EMOTE_STATS_COMMAND_NAME) .module(EmoteTrackingModuleDefinition.EMOTE_TRACKING) .templated(true) .messageCommandOnly(true) .async(true) + .slashCommandConfig(slashCommandConfig) .supportsEmbedException(true) .causesReaction(true) .parameters(parameters) @@ -109,4 +194,6 @@ public class EmoteStats extends AbstractConditionableCommand { public FeatureDefinition getFeature() { return StatisticFeatureDefinition.EMOTE_TRACKING; } + + } diff --git a/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/command/ExportEmoteStats.java b/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/command/ExportEmoteStats.java index 31126ebd7..9786f3532 100644 --- a/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/command/ExportEmoteStats.java +++ b/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/command/ExportEmoteStats.java @@ -42,8 +42,6 @@ import java.util.concurrent.CompletableFuture; @Slf4j public class ExportEmoteStats extends AbstractConditionableCommand { - public static final String DOWNLOAD_EMOTE_STATS_NO_STATS_AVAILABLE_RESPONSE_TEMPLATE_KEY = "downloadEmoteStats_no_stats_available_response"; - public static final String DOWNLOAD_EMOTE_STATS_RESPONSE_TEMPLATE_KEY = "downloadEmoteStats_response"; @Autowired private ServerManagementService serverManagementService; @@ -59,6 +57,11 @@ public class ExportEmoteStats extends AbstractConditionableCommand { @Autowired private FileService fileService; + public static final String DOWNLOAD_EMOTE_STATS_NO_STATS_AVAILABLE_RESPONSE_TEMPLATE_KEY = "downloadEmoteStats_no_stats_available_response"; + private static final String DOWNLOAD_EMOTE_STATS_RESPONSE_TEMPLATE_KEY = "downloadEmoteStats_response"; + private static final String EXPORT_EMOTE_STATS_COMMAND_NAME = "exportEmoteStats"; + private static final String EXPORT_EMOTE_STATS_PERIOD = "period"; + @Override public CompletableFuture executeAsync(CommandContext commandContext) { List parameters = commandContext.getParameters().getParameters(); @@ -99,7 +102,7 @@ public class ExportEmoteStats extends AbstractConditionableCommand { List parameters = new ArrayList<>(); Parameter periodParameter = Parameter .builder() - .name("period") + .name(EXPORT_EMOTE_STATS_PERIOD) .templated(true) .optional(true) .type(Duration.class) @@ -110,7 +113,7 @@ public class ExportEmoteStats extends AbstractConditionableCommand { .templated(true) .build(); return CommandConfiguration.builder() - .name("exportEmoteStats") + .name(EXPORT_EMOTE_STATS_COMMAND_NAME) .module(EmoteTrackingModuleDefinition.EMOTE_TRACKING) .templated(true) .async(true) diff --git a/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/command/ExternalEmoteStats.java b/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/command/ExternalEmoteStats.java index e3fad8586..d8a8653a5 100644 --- a/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/command/ExternalEmoteStats.java +++ b/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/command/ExternalEmoteStats.java @@ -8,17 +8,25 @@ import dev.sheldan.abstracto.core.command.execution.CommandContext; import dev.sheldan.abstracto.core.command.execution.CommandResult; import dev.sheldan.abstracto.core.config.FeatureDefinition; import dev.sheldan.abstracto.core.config.FeatureMode; +import dev.sheldan.abstracto.core.interaction.InteractionService; +import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig; +import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParameterService; import dev.sheldan.abstracto.core.models.database.AServer; import dev.sheldan.abstracto.core.service.ChannelService; import dev.sheldan.abstracto.core.service.management.ServerManagementService; import dev.sheldan.abstracto.core.utils.FutureUtils; +import dev.sheldan.abstracto.core.utils.ParseUtils; import dev.sheldan.abstracto.statistic.config.StatisticFeatureDefinition; +import dev.sheldan.abstracto.statistic.config.StatisticSlashCommandNames; +import dev.sheldan.abstracto.statistic.emote.command.parameter.UsedEmoteTypeParameter; import dev.sheldan.abstracto.statistic.emote.config.EmoteTrackingMode; import dev.sheldan.abstracto.statistic.emote.config.EmoteTrackingModuleDefinition; import dev.sheldan.abstracto.statistic.emote.model.EmoteStatsModel; import dev.sheldan.abstracto.statistic.emote.service.UsedEmoteService; +import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; import net.dv8tion.jda.api.entities.Message; +import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -46,8 +54,18 @@ public class ExternalEmoteStats extends AbstractConditionableCommand { @Autowired private ServerManagementService serverManagementService; + @Autowired + private SlashCommandParameterService slashCommandParameterService; + + @Autowired + private InteractionService interactionService; + public static final String EMOTE_STATS_STATIC_EXTERNAL_RESPONSE = "externalEmoteStats_static_response"; public static final String EMOTE_STATS_ANIMATED_EXTERNAL_RESPONSE = "externalEmoteStats_animated_response"; + private static final String EXTERNAL_EMOTE_STATS_USED_EMOTE_TYPE = "type"; + + private static final String EXTERNAL_EMOTE_STATS_PERIOD = "period"; + private static final String EXTERNAL_EMOTE_STATS_COMMAND_NAME = "externalEmoteStats"; @Override public CompletableFuture executeAsync(CommandContext commandContext) { @@ -60,7 +78,7 @@ public class ExternalEmoteStats extends AbstractConditionableCommand { statsSince = Instant.now().minus(duration); } AServer server = serverManagementService.loadServer(commandContext.getGuild()); - EmoteStatsModel emoteStatsModel = usedEmoteService.getExternalEmoteStatsForServerSince(server, statsSince); + EmoteStatsModel emoteStatsModel = usedEmoteService.getExternalEmoteStatsForServerSince(server, statsSince, null); List> messagePromises = new ArrayList<>(); // only show embed if static emote stats are available @@ -85,27 +103,102 @@ public class ExternalEmoteStats extends AbstractConditionableCommand { .thenApply(unused -> CommandResult.fromIgnored()); } + @Override + public CompletableFuture executeSlash(SlashCommandInteractionEvent event) { + UsedEmoteTypeParameter typeEnum; + if(slashCommandParameterService.hasCommandOption(EXTERNAL_EMOTE_STATS_USED_EMOTE_TYPE, event)) { + String type = slashCommandParameterService.getCommandOption(EXTERNAL_EMOTE_STATS_USED_EMOTE_TYPE, event, String.class); + typeEnum = UsedEmoteTypeParameter.valueOf(type); + } else { + typeEnum = null; + } + Instant startTime; + if(slashCommandParameterService.hasCommandOption(EXTERNAL_EMOTE_STATS_PERIOD, event)) { + String durationString = slashCommandParameterService.getCommandOption(EXTERNAL_EMOTE_STATS_PERIOD, event, Duration.class, String.class); + Duration durationSince = ParseUtils.parseDuration(durationString); + startTime = Instant.now().minus(durationSince); + } else { + startTime = Instant.EPOCH; + } + + AServer server = serverManagementService.loadServer(event.getGuild()); + EmoteStatsModel emoteStatsModel = usedEmoteService.getExternalEmoteStatsForServerSince(server, startTime, UsedEmoteTypeParameter.convertToUsedEmoteType(typeEnum)); + List> messagePromises = new ArrayList<>(); + return event.deferReply().submit().thenCompose(interactionHook -> { + // only show embed if static emote stats are available + if (!emoteStatsModel.getStaticEmotes().isEmpty()) { + log.debug("External emote stats has {} static emotes since {}.", emoteStatsModel.getStaticEmotes().size(), startTime); + messagePromises.addAll( + interactionService.sendMessageToInteraction(EMOTE_STATS_STATIC_EXTERNAL_RESPONSE, emoteStatsModel, interactionHook)); + } + + // only show embed if animated emote stats are available + if (!emoteStatsModel.getAnimatedEmotes().isEmpty()) { + log.debug("External emote stats has {} animated emotes since {}.", emoteStatsModel.getAnimatedEmotes(), startTime); + messagePromises.addAll( + interactionService.sendMessageToInteraction(EMOTE_STATS_ANIMATED_EXTERNAL_RESPONSE, emoteStatsModel, interactionHook)); + } + + // show an embed if no emote stats are available indicating so + if (!emoteStatsModel.areStatsAvailable()) { + log.info("No external emote stats available for guild {} since {}.", event.getGuild().getIdLong(), startTime); + messagePromises.addAll(interactionService.sendMessageToInteraction(EmoteStats.EMOTE_STATS_NO_STATS_AVAILABLE, new Object(), interactionHook)); + } + + return FutureUtils.toSingleFutureGeneric(messagePromises) + .thenApply(unused -> CommandResult.fromIgnored()); + }); + } + @Override public CommandConfiguration getConfiguration() { List parameters = new ArrayList<>(); Parameter periodParameter = Parameter .builder() - .name("period") + .name(EXTERNAL_EMOTE_STATS_PERIOD) .templated(true) .optional(true) .type(Duration.class) .build(); parameters.add(periodParameter); + HelpInfo helpInfo = HelpInfo .builder() .templated(true) .build(); + + List emoteTypes = Arrays + .stream(UsedEmoteTypeParameter.values()) + .map(Enum::name) + .collect(Collectors.toList()); + + Parameter typeParameter = Parameter + .builder() + .name(EXTERNAL_EMOTE_STATS_USED_EMOTE_TYPE) + .templated(true) + .slashCommandOnly(true) + .optional(true) + .choices(emoteTypes) + .type(String.class) + .build(); + + parameters.add(typeParameter); + + SlashCommandConfig slashCommandConfig = SlashCommandConfig + .builder() + .enabled(true) + .rootCommandName(StatisticSlashCommandNames.STATISTIC) + .groupName("emotestats") + .commandName("external") + .build(); + return CommandConfiguration.builder() - .name("externalEmoteStats") + .name(EXTERNAL_EMOTE_STATS_COMMAND_NAME) .module(EmoteTrackingModuleDefinition.EMOTE_TRACKING) .templated(true) .messageCommandOnly(true) .async(true) + .slashCommandConfig(slashCommandConfig) .supportsEmbedException(true) .causesReaction(true) .parameters(parameters) diff --git a/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/command/PurgeEmoteStats.java b/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/command/PurgeEmoteStats.java index 072602418..f66924665 100644 --- a/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/command/PurgeEmoteStats.java +++ b/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/command/PurgeEmoteStats.java @@ -7,11 +7,22 @@ import dev.sheldan.abstracto.core.command.config.Parameter; import dev.sheldan.abstracto.core.command.execution.CommandContext; import dev.sheldan.abstracto.core.command.execution.CommandResult; import dev.sheldan.abstracto.core.config.FeatureDefinition; +import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig; +import dev.sheldan.abstracto.core.interaction.slash.SlashCommandService; +import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParameterService; +import dev.sheldan.abstracto.core.models.ServerSpecificId; +import dev.sheldan.abstracto.core.utils.ParseUtils; import dev.sheldan.abstracto.statistic.config.StatisticFeatureDefinition; +import dev.sheldan.abstracto.statistic.config.StatisticSlashCommandNames; import dev.sheldan.abstracto.statistic.emote.config.EmoteTrackingModuleDefinition; +import dev.sheldan.abstracto.statistic.emote.exception.TrackedEmoteNotFoundException; import dev.sheldan.abstracto.statistic.emote.model.database.TrackedEmote; import dev.sheldan.abstracto.statistic.emote.service.UsedEmoteService; import dev.sheldan.abstracto.statistic.emote.service.management.TrackedEmoteManagementService; +import java.util.concurrent.CompletableFuture; +import net.dv8tion.jda.api.entities.emoji.CustomEmoji; +import net.dv8tion.jda.api.entities.emoji.Emoji; +import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -33,6 +44,17 @@ public class PurgeEmoteStats extends AbstractConditionableCommand { @Autowired private UsedEmoteService usedEmoteService; + @Autowired + private SlashCommandParameterService slashCommandParameterService; + + @Autowired + private SlashCommandService slashCommandService; + + private static final String PURGE_EMOTE_STATS_COMMAND_NAME = "purgeEmoteStats"; + private static final String PURGE_EMOTE_STATS_TRACKED_EMOTE = "trackedEmote"; + private static final String PURGE_EMOTE_STATS_PERIOD = "period"; + private static final String PURGE_EMOTE_STATS_RESPONSE = "purgeEmoteStats_response"; + @Override public CommandResult execute(CommandContext commandContext) { List parameters = commandContext.getParameters().getParameters(); @@ -49,30 +71,65 @@ public class PurgeEmoteStats extends AbstractConditionableCommand { return CommandResult.fromSuccess(); } + @Override + public CompletableFuture executeSlash(SlashCommandInteractionEvent event) { + String emote = slashCommandParameterService.getCommandOption(PURGE_EMOTE_STATS_TRACKED_EMOTE, event, String.class); + Emoji emoji = slashCommandParameterService.loadEmoteFromString(emote, event.getGuild()); + if(!(emoji instanceof CustomEmoji)) { + throw new TrackedEmoteNotFoundException(); + } + Long emoteId = ((CustomEmoji) emoji).getIdLong(); + TrackedEmote trackedEmote = trackedEmoteManagementService.loadByTrackedEmoteServer(new ServerSpecificId(event.getGuild().getIdLong(), emoteId)); + // default 1.1.1970 + Instant since = Instant.EPOCH; + if(slashCommandParameterService.hasCommandOption(PURGE_EMOTE_STATS_PERIOD, event)) { + // if a Duration is given, subtract it from the current point in time + String durationString = slashCommandParameterService.getCommandOption(PURGE_EMOTE_STATS_PERIOD, event, Duration.class, String.class); + Duration duration = ParseUtils.parseDuration(durationString); + since = Instant.now().minus(duration); + } + usedEmoteService.purgeEmoteUsagesSince(trackedEmote, since); + return slashCommandService.completeConfirmableCommand(event, PURGE_EMOTE_STATS_RESPONSE); + } + @Override public CommandConfiguration getConfiguration() { List parameters = new ArrayList<>(); Parameter trackedEmoteParameter = Parameter .builder() - .name("trackedEmote") + .name(PURGE_EMOTE_STATS_TRACKED_EMOTE) .templated(true) .type(TrackedEmote.class) .build(); parameters.add(trackedEmoteParameter); Parameter periodParameter = Parameter .builder() - .name("period") + .name(PURGE_EMOTE_STATS_PERIOD) .templated(true) .optional(true) .type(Duration.class) .build(); parameters.add(periodParameter); - HelpInfo helpInfo = HelpInfo.builder().templated(true).build(); + + HelpInfo helpInfo = HelpInfo + .builder() + .templated(true) + .build(); + + SlashCommandConfig slashCommandConfig = SlashCommandConfig + .builder() + .enabled(true) + .rootCommandName(StatisticSlashCommandNames.STATISTIC_INTERNAL) + .groupName("manage") + .commandName("purgeemotestats") + .build(); + return CommandConfiguration.builder() - .name("purgeEmoteStats") + .name(PURGE_EMOTE_STATS_COMMAND_NAME) .module(EmoteTrackingModuleDefinition.EMOTE_TRACKING) .templated(true) .messageCommandOnly(true) + .slashCommandConfig(slashCommandConfig) .supportsEmbedException(true) .requiresConfirmation(true) .causesReaction(true) diff --git a/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/command/ResetEmoteStats.java b/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/command/ResetEmoteStats.java index 005bb9f7b..30aae329d 100644 --- a/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/command/ResetEmoteStats.java +++ b/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/command/ResetEmoteStats.java @@ -7,9 +7,14 @@ import dev.sheldan.abstracto.core.command.config.Parameter; import dev.sheldan.abstracto.core.command.execution.CommandContext; import dev.sheldan.abstracto.core.command.execution.CommandResult; import dev.sheldan.abstracto.core.config.FeatureDefinition; +import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig; +import dev.sheldan.abstracto.core.interaction.slash.SlashCommandService; import dev.sheldan.abstracto.statistic.config.StatisticFeatureDefinition; +import dev.sheldan.abstracto.statistic.config.StatisticSlashCommandNames; import dev.sheldan.abstracto.statistic.emote.config.EmoteTrackingModuleDefinition; import dev.sheldan.abstracto.statistic.emote.service.TrackedEmoteService; +import java.util.concurrent.CompletableFuture; +import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -26,21 +31,47 @@ public class ResetEmoteStats extends AbstractConditionableCommand { @Autowired private TrackedEmoteService trackedEmoteService; + @Autowired + private SlashCommandService slashCommandService; + + private static final String RESET_EMOTE_STATS_COMMAND_NAME = "resetEmoteStats"; + private static final String RESET_EMOTE_STATS_RESPONSE = "resetEmoteStats_response"; + @Override public CommandResult execute(CommandContext commandContext) { trackedEmoteService.resetEmoteStats(commandContext.getGuild()); return CommandResult.fromSuccess(); } + @Override + public CompletableFuture executeSlash(SlashCommandInteractionEvent event) { + trackedEmoteService.resetEmoteStats(event.getGuild()); + return slashCommandService.completeConfirmableCommand(event, RESET_EMOTE_STATS_RESPONSE); + } + @Override public CommandConfiguration getConfiguration() { List parameters = new ArrayList<>(); - HelpInfo helpInfo = HelpInfo.builder().templated(true).build(); + + HelpInfo helpInfo = HelpInfo + .builder() + .templated(true) + .build(); + + SlashCommandConfig slashCommandConfig = SlashCommandConfig + .builder() + .enabled(true) + .rootCommandName(StatisticSlashCommandNames.STATISTIC_INTERNAL) + .groupName("manage") + .commandName("resetemotestats") + .build(); + return CommandConfiguration.builder() - .name("resetEmoteStats") + .name(RESET_EMOTE_STATS_COMMAND_NAME) .messageCommandOnly(true) .module(EmoteTrackingModuleDefinition.EMOTE_TRACKING) .templated(true) + .slashCommandConfig(slashCommandConfig) .supportsEmbedException(true) .requiresConfirmation(true) .causesReaction(true) diff --git a/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/command/ShowExternalTrackedEmote.java b/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/command/ShowExternalTrackedEmote.java index 943f60497..b37d13394 100644 --- a/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/command/ShowExternalTrackedEmote.java +++ b/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/command/ShowExternalTrackedEmote.java @@ -9,13 +9,22 @@ import dev.sheldan.abstracto.core.command.execution.CommandContext; import dev.sheldan.abstracto.core.command.execution.CommandResult; import dev.sheldan.abstracto.core.config.FeatureDefinition; import dev.sheldan.abstracto.core.config.FeatureMode; +import dev.sheldan.abstracto.core.interaction.InteractionService; +import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig; +import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParameterService; +import dev.sheldan.abstracto.core.models.ServerSpecificId; import dev.sheldan.abstracto.core.service.ChannelService; import dev.sheldan.abstracto.core.utils.FutureUtils; import dev.sheldan.abstracto.statistic.config.StatisticFeatureDefinition; +import dev.sheldan.abstracto.statistic.config.StatisticSlashCommandNames; import dev.sheldan.abstracto.statistic.emote.config.EmoteTrackingMode; import dev.sheldan.abstracto.statistic.emote.config.EmoteTrackingModuleDefinition; +import dev.sheldan.abstracto.statistic.emote.exception.TrackedEmoteNotFoundException; import dev.sheldan.abstracto.statistic.emote.model.database.TrackedEmote; import dev.sheldan.abstracto.statistic.emote.service.management.TrackedEmoteManagementService; +import net.dv8tion.jda.api.entities.emoji.CustomEmoji; +import net.dv8tion.jda.api.entities.emoji.Emoji; +import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -31,13 +40,22 @@ import java.util.concurrent.CompletableFuture; @Component public class ShowExternalTrackedEmote extends AbstractConditionableCommand { - public static final String SHOW_EXTERNAL_TRACKED_EMOTE_RESPONSE_TEMPLATE_KEY = "showExternalTrackedEmote_response"; @Autowired private ChannelService channelService; @Autowired private TrackedEmoteManagementService trackedEmoteManagementService; + @Autowired + private SlashCommandParameterService slashCommandParameterService; + + @Autowired + private InteractionService interactionService; + + public static final String SHOW_EXTERNAL_TRACKED_EMOTE_RESPONSE_TEMPLATE_KEY = "showExternalTrackedEmote_response"; + private static final String SHOW_EXTERNAL_TRACKED_EMOTE_COMMAND_NAME = "showExternalTrackedEmote"; + private static final String SHOW_EXTERNAL_TRACKED_EMOTE_TRACKED_EMOTE = "trackedEmote"; + @Override public CompletableFuture executeAsync(CommandContext commandContext) { List parameters = commandContext.getParameters().getParameters(); @@ -52,22 +70,53 @@ public class ShowExternalTrackedEmote extends AbstractConditionableCommand { .thenApply(unused -> CommandResult.fromIgnored()); } + @Override + public CompletableFuture executeSlash(SlashCommandInteractionEvent event) { + String emote = slashCommandParameterService.getCommandOption(SHOW_EXTERNAL_TRACKED_EMOTE_TRACKED_EMOTE, event, String.class); + Emoji emoji = slashCommandParameterService.loadEmoteFromString(emote, event.getGuild()); + if(emoji instanceof CustomEmoji) { + Long emoteId = ((CustomEmoji) emoji).getIdLong(); + TrackedEmote trackedEmote = trackedEmoteManagementService.loadByTrackedEmoteServer(new ServerSpecificId(event.getGuild().getIdLong(), emoteId)); + if(!trackedEmote.getExternal()) { + throw new AbstractoTemplatedException("Emote is not external", "showExternalTrackedEmote_emote_is_not_external"); + } + return interactionService.replyEmbed(SHOW_EXTERNAL_TRACKED_EMOTE_RESPONSE_TEMPLATE_KEY, trackedEmote, event) + .thenApply(unused -> CommandResult.fromIgnored()); + } else { + throw new TrackedEmoteNotFoundException(); + } + } + @Override public CommandConfiguration getConfiguration() { List parameters = new ArrayList<>(); Parameter trackedEmoteParameter = Parameter .builder() - .name("trackedEmote") + .name(SHOW_EXTERNAL_TRACKED_EMOTE_TRACKED_EMOTE) .templated(true) .type(TrackedEmote.class) .build(); parameters.add(trackedEmoteParameter); - HelpInfo helpInfo = HelpInfo.builder().templated(true).build(); + + HelpInfo helpInfo = HelpInfo + .builder() + .templated(true) + .build(); + + SlashCommandConfig slashCommandConfig = SlashCommandConfig + .builder() + .enabled(true) + .rootCommandName(StatisticSlashCommandNames.STATISTIC) + .groupName("show") + .commandName("externaltrackedemote") + .build(); + return CommandConfiguration.builder() - .name("showExternalTrackedEmote") + .name(SHOW_EXTERNAL_TRACKED_EMOTE_COMMAND_NAME) .module(EmoteTrackingModuleDefinition.EMOTE_TRACKING) .templated(true) .async(true) + .slashCommandConfig(slashCommandConfig) .messageCommandOnly(true) .supportsEmbedException(true) .causesReaction(true) diff --git a/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/command/ShowTrackedEmotes.java b/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/command/ShowTrackedEmotes.java index 8d2a9a436..a3071c563 100644 --- a/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/command/ShowTrackedEmotes.java +++ b/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/command/ShowTrackedEmotes.java @@ -7,15 +7,20 @@ import dev.sheldan.abstracto.core.command.config.Parameter; import dev.sheldan.abstracto.core.command.execution.CommandContext; import dev.sheldan.abstracto.core.command.execution.CommandResult; import dev.sheldan.abstracto.core.config.FeatureDefinition; +import dev.sheldan.abstracto.core.interaction.InteractionService; +import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig; +import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParameterService; import dev.sheldan.abstracto.core.service.ChannelService; import dev.sheldan.abstracto.core.service.FeatureModeService; import dev.sheldan.abstracto.core.utils.FutureUtils; import dev.sheldan.abstracto.statistic.config.StatisticFeatureDefinition; +import dev.sheldan.abstracto.statistic.config.StatisticSlashCommandNames; import dev.sheldan.abstracto.statistic.emote.config.EmoteTrackingMode; import dev.sheldan.abstracto.statistic.emote.config.EmoteTrackingModuleDefinition; import dev.sheldan.abstracto.statistic.emote.model.TrackedEmoteOverview; import dev.sheldan.abstracto.statistic.emote.service.TrackedEmoteService; import net.dv8tion.jda.api.entities.Message; +import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -40,6 +45,12 @@ public class ShowTrackedEmotes extends AbstractConditionableCommand { @Autowired private FeatureModeService featureModeService; + @Autowired + private SlashCommandParameterService slashCommandParameterService; + + @Autowired + private InteractionService interactionService; + public static final String SHOW_TRACKED_EMOTES_STATIC_RESPONSE = "showTrackedEmotes_static_response"; public static final String SHOW_TRACKED_EMOTES_ANIMATED_RESPONSE = "showTrackedEmotes_animated_response"; public static final String SHOW_TRACKED_EMOTES_EXTERNAL_ANIMATED_RESPONSE = "showTrackedEmotes_external_animated_response"; @@ -48,9 +59,11 @@ public class ShowTrackedEmotes extends AbstractConditionableCommand { public static final String SHOW_TRACKED_EMOTES_DELETED_ANIMATED_RESPONSE = "showTrackedEmotes_deleted_animated_response"; public static final String SHOW_TRACKED_EMOTES_NO_STATS_AVAILABLE = "showTrackedEmotes_no_emotes_available"; + private static final String SHOW_TRACKED_EMOTES_COMMAND_NAME = "showTrackedEmotes"; + private static final String SHOW_TRACKED_EMOTES_SHOW_ALL = "showAll"; + @Override public CompletableFuture executeAsync(CommandContext commandContext) { - // per default, do not show TrackedEmote for which tracking has been disabled Boolean showTrackingDisabled = false; if(!commandContext.getParameters().getParameters().isEmpty()) { @@ -110,23 +123,98 @@ public class ShowTrackedEmotes extends AbstractConditionableCommand { .thenApply(unused -> CommandResult.fromIgnored()); } + @Override + public CompletableFuture executeSlash(SlashCommandInteractionEvent event) { + // per default, do not show TrackedEmote for which tracking has been disabled + Boolean showTrackingDisabled = false; + if(slashCommandParameterService.hasCommandOption(SHOW_TRACKED_EMOTES_SHOW_ALL, event)) { + showTrackingDisabled = slashCommandParameterService.getCommandOption(SHOW_TRACKED_EMOTES_SHOW_ALL, event, Boolean.class); + } + + TrackedEmoteOverview trackedEmoteOverview = trackedEmoteService.loadTrackedEmoteOverview(event.getGuild(), showTrackingDisabled); + List> messagePromises = new ArrayList<>(); + return event.deferReply().submit().thenCompose(interactionHook -> { + boolean noTrackedEmotesAvailable = true; + // only show the embed, if there are static tracked emotes + if(!trackedEmoteOverview.getStaticEmotes().isEmpty()) { + noTrackedEmotesAvailable = false; + messagePromises.addAll(interactionService.sendMessageToInteraction(SHOW_TRACKED_EMOTES_STATIC_RESPONSE, trackedEmoteOverview, interactionHook)); + } + + // only show the embed if there are animated tracked emotes + if(!trackedEmoteOverview.getAnimatedEmotes().isEmpty()) { + noTrackedEmotesAvailable = false; + messagePromises.addAll(interactionService.sendMessageToInteraction(SHOW_TRACKED_EMOTES_ANIMATED_RESPONSE, trackedEmoteOverview, interactionHook)); + } + + // only show the embed, if there are deleted static emotes + if(!trackedEmoteOverview.getDeletedStaticEmotes().isEmpty()) { + noTrackedEmotesAvailable = false; + messagePromises.addAll(interactionService.sendMessageToInteraction(SHOW_TRACKED_EMOTES_DELETED_STATIC_RESPONSE, trackedEmoteOverview, interactionHook)); + } + + // only show the embed, if there are deleted animated emotes + if(!trackedEmoteOverview.getDeletedAnimatedEmotes().isEmpty()) { + noTrackedEmotesAvailable = false; + messagePromises.addAll(interactionService.sendMessageToInteraction(SHOW_TRACKED_EMOTES_DELETED_ANIMATED_RESPONSE, trackedEmoteOverview, interactionHook)); + } + + boolean externalTrackingEnabled = featureModeService.featureModeActive(StatisticFeatureDefinition.EMOTE_TRACKING, event.getGuild().getIdLong(), EmoteTrackingMode.EXTERNAL_EMOTES); + + // only show external emotes if external emotes are enabled + if(externalTrackingEnabled) { + + // only show the embed if there are external static emotes + if(!trackedEmoteOverview.getExternalStaticEmotes().isEmpty()) { + noTrackedEmotesAvailable = false; + messagePromises.addAll(interactionService.sendMessageToInteraction(SHOW_TRACKED_EMOTES_EXTERNAL_STATIC_RESPONSE, trackedEmoteOverview, interactionHook)); + } + + // only show the embed if there are external animated emotes + if(!trackedEmoteOverview.getExternalAnimatedEmotes().isEmpty()) { + noTrackedEmotesAvailable = false; + messagePromises.addAll(interactionService.sendMessageToInteraction(SHOW_TRACKED_EMOTES_EXTERNAL_ANIMATED_RESPONSE, trackedEmoteOverview, interactionHook)); + } + } + + // if there are no tracked emotes available, show an embed indicating so + if(noTrackedEmotesAvailable) { + messagePromises.addAll(interactionService.sendMessageToInteraction(SHOW_TRACKED_EMOTES_NO_STATS_AVAILABLE, new Object(), interactionHook)); + } + return FutureUtils.toSingleFutureGeneric(messagePromises); + }).thenApply(unused -> CommandResult.fromSuccess()); + } + @Override public CommandConfiguration getConfiguration() { List parameters = new ArrayList<>(); - HelpInfo helpInfo = HelpInfo.builder().templated(true).build(); + HelpInfo helpInfo = HelpInfo + .builder() + .templated(true) + .build(); Parameter showAllParameter = Parameter .builder() - .name("showAll") + .name(SHOW_TRACKED_EMOTES_SHOW_ALL) .templated(true) .optional(true) .type(Boolean.class) .build(); parameters.add(showAllParameter); + + SlashCommandConfig slashCommandConfig = SlashCommandConfig + .builder() + .enabled(true) + .rootCommandName(StatisticSlashCommandNames.STATISTIC) + .groupName("show") + .commandName("trackedemotes") + .build(); + return CommandConfiguration.builder() - .name("showTrackedEmotes") + .name(SHOW_TRACKED_EMOTES_COMMAND_NAME) .module(EmoteTrackingModuleDefinition.EMOTE_TRACKING) .templated(true) .async(true) + .slashCommandConfig(slashCommandConfig) .messageCommandOnly(true) .supportsEmbedException(true) .causesReaction(true) diff --git a/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/command/SyncTrackedEmotes.java b/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/command/SyncTrackedEmotes.java index 31ac15a5e..f0e9fd394 100644 --- a/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/command/SyncTrackedEmotes.java +++ b/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/command/SyncTrackedEmotes.java @@ -7,12 +7,16 @@ import dev.sheldan.abstracto.core.command.config.Parameter; import dev.sheldan.abstracto.core.command.execution.CommandContext; import dev.sheldan.abstracto.core.command.execution.CommandResult; import dev.sheldan.abstracto.core.config.FeatureDefinition; +import dev.sheldan.abstracto.core.interaction.InteractionService; +import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig; import dev.sheldan.abstracto.core.service.ChannelService; import dev.sheldan.abstracto.core.utils.FutureUtils; import dev.sheldan.abstracto.statistic.config.StatisticFeatureDefinition; +import dev.sheldan.abstracto.statistic.config.StatisticSlashCommandNames; import dev.sheldan.abstracto.statistic.emote.config.EmoteTrackingModuleDefinition; import dev.sheldan.abstracto.statistic.emote.model.TrackedEmoteSynchronizationResult; import dev.sheldan.abstracto.statistic.emote.service.TrackedEmoteService; +import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -28,13 +32,18 @@ import java.util.concurrent.CompletableFuture; @Component public class SyncTrackedEmotes extends AbstractConditionableCommand { - public static final String SYNC_TRACKED_EMOTES_RESULT_RESPONSE = "syncTrackedEmotes_result_response"; @Autowired private TrackedEmoteService trackedEmoteService; @Autowired private ChannelService channelService; + @Autowired + private InteractionService interactionService; + + static final String SYNC_TRACKED_EMOTES_RESULT_RESPONSE = "syncTrackedEmotes_result_response"; + private static final String SYNC_TRACKED_EMOTES_COMMAND_NAME = "syncTrackedEmotes"; + @Override public CompletableFuture executeAsync(CommandContext commandContext) { TrackedEmoteSynchronizationResult syncResult = trackedEmoteService.synchronizeTrackedEmotes(commandContext.getGuild()); @@ -43,6 +52,14 @@ public class SyncTrackedEmotes extends AbstractConditionableCommand { .thenApply(unused -> CommandResult.fromIgnored()); } + @Override + public CompletableFuture executeSlash(SlashCommandInteractionEvent event) { + TrackedEmoteSynchronizationResult syncResult = trackedEmoteService.synchronizeTrackedEmotes(event.getGuild()); + // show a result of how many emotes were deleted/added + return interactionService.replyEmbed(SYNC_TRACKED_EMOTES_RESULT_RESPONSE, syncResult, event) + .thenApply(unused -> CommandResult.fromIgnored()); + } + @Override public CommandConfiguration getConfiguration() { List parameters = new ArrayList<>(); @@ -50,10 +67,20 @@ public class SyncTrackedEmotes extends AbstractConditionableCommand { .builder() .templated(true) .build(); + + SlashCommandConfig slashCommandConfig = SlashCommandConfig + .builder() + .enabled(true) + .rootCommandName(StatisticSlashCommandNames.STATISTIC_INTERNAL) + .groupName("manage") + .commandName("synctrackedemotes") + .build(); + return CommandConfiguration.builder() - .name("syncTrackedEmotes") + .name(SYNC_TRACKED_EMOTES_COMMAND_NAME) .module(EmoteTrackingModuleDefinition.EMOTE_TRACKING) .templated(true) + .slashCommandConfig(slashCommandConfig) .messageCommandOnly(true) .async(true) .supportsEmbedException(true) diff --git a/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/command/TrackEmote.java b/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/command/TrackEmote.java index 021b3a8ce..870154964 100644 --- a/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/command/TrackEmote.java +++ b/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/command/TrackEmote.java @@ -8,15 +8,24 @@ import dev.sheldan.abstracto.core.command.exception.IncorrectParameterException; import dev.sheldan.abstracto.core.command.execution.CommandContext; import dev.sheldan.abstracto.core.command.execution.CommandResult; import dev.sheldan.abstracto.core.config.FeatureDefinition; +import dev.sheldan.abstracto.core.interaction.InteractionService; +import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig; +import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParameterService; import dev.sheldan.abstracto.core.service.EmoteService; import dev.sheldan.abstracto.core.service.FeatureModeService; import dev.sheldan.abstracto.statistic.config.StatisticFeatureDefinition; +import dev.sheldan.abstracto.statistic.config.StatisticSlashCommandNames; import dev.sheldan.abstracto.statistic.emote.command.parameter.TrackEmoteParameter; import dev.sheldan.abstracto.statistic.emote.config.EmoteTrackingMode; import dev.sheldan.abstracto.statistic.emote.config.EmoteTrackingModuleDefinition; +import dev.sheldan.abstracto.statistic.emote.exception.TrackedEmoteNotFoundException; import dev.sheldan.abstracto.statistic.emote.model.database.TrackedEmote; import dev.sheldan.abstracto.statistic.emote.service.TrackedEmoteService; import dev.sheldan.abstracto.statistic.emote.service.management.TrackedEmoteManagementService; +import java.util.concurrent.CompletableFuture; +import net.dv8tion.jda.api.entities.emoji.CustomEmoji; +import net.dv8tion.jda.api.entities.emoji.Emoji; +import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -42,6 +51,17 @@ public class TrackEmote extends AbstractConditionableCommand { @Autowired private FeatureModeService featureModeService; + @Autowired + private SlashCommandParameterService slashCommandParameterService; + + @Autowired + private InteractionService interactionService; + + private static final String TRACK_EMOTE_COMMAND_NAME = "trackEmote"; + private static final String TRACK_EMOTE_EMOTE = "emote"; + + private static final String TRACK_EMOTE_RESPONSE = "trackEmote_response"; + @Override public CommandResult execute(CommandContext commandContext) { TrackEmoteParameter emoteToTrack = (TrackEmoteParameter) commandContext.getParameters().getParameters().get(0); @@ -66,22 +86,66 @@ public class TrackEmote extends AbstractConditionableCommand { return CommandResult.fromSuccess(); } + @Override + public CompletableFuture executeSlash(SlashCommandInteractionEvent event) { + String emote = slashCommandParameterService.getCommandOption(TRACK_EMOTE_EMOTE, event, String.class); + Emoji emoji = slashCommandParameterService.loadEmoteFromString(emote, event.getGuild()); + if(emoji instanceof CustomEmoji customEmoji) { + Long emoteId = customEmoji.getIdLong(); + long serverId = event.getGuild().getIdLong(); + // if its already a tracked emote, just set the tracking_enabled flag to true + if(trackedEmoteManagementService.trackedEmoteExists(emoteId, serverId)) { + TrackedEmote trackedemote = trackedEmoteManagementService.loadByEmoteId(emoteId, serverId); + trackedEmoteManagementService.enableTrackedEmote(trackedemote); + return interactionService.replyEmbed(TRACK_EMOTE_RESPONSE, event) + .thenApply(interactionHook -> CommandResult.fromIgnored()); + } else { + // if its a new emote, lets see if its external + boolean external = !emoteService.emoteIsFromGuild(customEmoji, event.getGuild()); + if (external) + { + // this throws an exception if the feature mode is not enabled + featureModeService.validateActiveFeatureMode(serverId, StatisticFeatureDefinition.EMOTE_TRACKING, EmoteTrackingMode.EXTERNAL_EMOTES); + } + trackedEmoteService.createTrackedEmote(customEmoji, event.getGuild(), external); + return interactionService.replyEmbed(TRACK_EMOTE_RESPONSE, event) + .thenApply(interactionHook -> CommandResult.fromIgnored()); + } + } else { + throw new TrackedEmoteNotFoundException(); + } + } + @Override public CommandConfiguration getConfiguration() { List parameters = new ArrayList<>(); Parameter emoteParameter = Parameter .builder() - .name("emote") + .name(TRACK_EMOTE_EMOTE) .templated(true) .type(TrackEmoteParameter.class) .build(); parameters.add(emoteParameter); - HelpInfo helpInfo = HelpInfo.builder().templated(true).build(); + + HelpInfo helpInfo = HelpInfo + .builder() + .templated(true) + .build(); + + SlashCommandConfig slashCommandConfig = SlashCommandConfig + .builder() + .enabled(true) + .rootCommandName(StatisticSlashCommandNames.STATISTIC_INTERNAL) + .groupName("manage") + .commandName("trackemote") + .build(); + return CommandConfiguration.builder() - .name("trackEmote") + .name(TRACK_EMOTE_COMMAND_NAME) .module(EmoteTrackingModuleDefinition.EMOTE_TRACKING) .templated(true) .messageCommandOnly(true) + .slashCommandConfig(slashCommandConfig) .supportsEmbedException(true) .causesReaction(true) .parameters(parameters) diff --git a/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/command/parameter/UsedEmoteTypeParameter.java b/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/command/parameter/UsedEmoteTypeParameter.java new file mode 100644 index 000000000..a53fb7d00 --- /dev/null +++ b/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/command/parameter/UsedEmoteTypeParameter.java @@ -0,0 +1,18 @@ +package dev.sheldan.abstracto.statistic.emote.command.parameter; + +import dev.sheldan.abstracto.statistic.emote.model.database.UsedEmoteType; + +public enum UsedEmoteTypeParameter { + REACTION, + MESSAGE; + + public static UsedEmoteType convertToUsedEmoteType(UsedEmoteTypeParameter usedEmoteTypeParameter) { + if(usedEmoteTypeParameter == null) { + return null; + } + return switch (usedEmoteTypeParameter) { + case MESSAGE -> UsedEmoteType.MESSAGE; + case REACTION -> UsedEmoteType.REACTION; + }; + } +} diff --git a/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/command/parameter/handler/TrackEmoteParameterSlashCommandParameterProvider.java b/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/command/parameter/handler/TrackEmoteParameterSlashCommandParameterProvider.java new file mode 100644 index 000000000..3cf8207e6 --- /dev/null +++ b/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/command/parameter/handler/TrackEmoteParameterSlashCommandParameterProvider.java @@ -0,0 +1,20 @@ +package dev.sheldan.abstracto.statistic.emote.command.parameter.handler; + +import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandOptionTypeMapping; +import dev.sheldan.abstracto.core.interaction.slash.parameter.provider.SlashCommandParameterProvider; +import dev.sheldan.abstracto.statistic.emote.command.parameter.TrackEmoteParameter; +import java.util.Arrays; +import net.dv8tion.jda.api.interactions.commands.OptionType; +import org.springframework.stereotype.Component; + +@Component +public class TrackEmoteParameterSlashCommandParameterProvider implements SlashCommandParameterProvider { + @Override + public SlashCommandOptionTypeMapping getOptionMapping() { + return SlashCommandOptionTypeMapping + .builder() + .type(TrackEmoteParameter.class) + .optionTypes(Arrays.asList(OptionType.STRING)) + .build(); + } +} diff --git a/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/command/parameter/handler/TrackedEmoteSlashCommandParameterProvider.java b/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/command/parameter/handler/TrackedEmoteSlashCommandParameterProvider.java new file mode 100644 index 000000000..8a63dfa37 --- /dev/null +++ b/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/command/parameter/handler/TrackedEmoteSlashCommandParameterProvider.java @@ -0,0 +1,20 @@ +package dev.sheldan.abstracto.statistic.emote.command.parameter.handler; + +import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandOptionTypeMapping; +import dev.sheldan.abstracto.core.interaction.slash.parameter.provider.SlashCommandParameterProvider; +import dev.sheldan.abstracto.statistic.emote.model.database.TrackedEmote; +import java.util.Arrays; +import net.dv8tion.jda.api.interactions.commands.OptionType; +import org.springframework.stereotype.Component; + +@Component +public class TrackedEmoteSlashCommandParameterProvider implements SlashCommandParameterProvider { + @Override + public SlashCommandOptionTypeMapping getOptionMapping() { + return SlashCommandOptionTypeMapping + .builder() + .type(TrackedEmote.class) + .optionTypes(Arrays.asList(OptionType.STRING)) + .build(); + } +} diff --git a/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/job/EmoteCleanupJob.java b/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/job/EmoteCleanupJob.java new file mode 100644 index 000000000..4e5a0ec87 --- /dev/null +++ b/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/job/EmoteCleanupJob.java @@ -0,0 +1,35 @@ +package dev.sheldan.abstracto.statistic.emote.job; + +import dev.sheldan.abstracto.statistic.emote.service.RunTimeReactionEmotesService; +import lombok.extern.slf4j.Slf4j; +import org.quartz.DisallowConcurrentExecution; +import org.quartz.JobExecutionContext; +import org.quartz.JobExecutionException; +import org.quartz.PersistJobDataAfterExecution; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.quartz.QuartzJobBean; +import org.springframework.stereotype.Component; + + +@Slf4j +@DisallowConcurrentExecution +@Component +@PersistJobDataAfterExecution +public class EmoteCleanupJob extends QuartzJobBean { + + @Autowired + private RunTimeReactionEmotesService runtimeReactionEmotesService; + + @Override + protected void executeInternal(JobExecutionContext context) throws JobExecutionException { + log.info("Cleaning up emote runtime storage."); + try { + runtimeReactionEmotesService.cleanupRunTimeStorage(); + } catch (Exception e) { + log.error("Failed to cleanup reaction runtimes.", e); + } + } + + +} + diff --git a/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/listener/EmoteTrackingListener.java b/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/listener/EmoteTrackingListener.java index a73fbb304..f05d35d92 100644 --- a/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/listener/EmoteTrackingListener.java +++ b/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/listener/EmoteTrackingListener.java @@ -6,6 +6,7 @@ import dev.sheldan.abstracto.core.listener.async.jda.AsyncMessageReceivedListene import dev.sheldan.abstracto.core.models.listener.MessageReceivedModel; import dev.sheldan.abstracto.core.service.GuildService; import dev.sheldan.abstracto.statistic.config.StatisticFeatureDefinition; +import dev.sheldan.abstracto.statistic.emote.model.database.UsedEmoteType; import dev.sheldan.abstracto.statistic.emote.service.TrackedEmoteService; import net.dv8tion.jda.api.entities.Message; import net.dv8tion.jda.api.entities.emoji.CustomEmoji; @@ -46,7 +47,7 @@ public class EmoteTrackingListener implements AsyncMessageReceivedListener { .stream() .collect(Collectors.groupingBy(CustomEmoji::getIdLong)); collect.values().forEach(groupedEmotes -> - trackedEmoteService.addEmoteToRuntimeStorage(groupedEmotes.get(0), guildService.getGuildById(model.getServerId()), (long) groupedEmotes.size()) + trackedEmoteService.addEmoteToRuntimeStorage(groupedEmotes.get(0), guildService.getGuildById(model.getServerId()), (long) groupedEmotes.size(), UsedEmoteType.MESSAGE) ); return DefaultListenerResult.PROCESSED; } diff --git a/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/listener/EmoteTrackingReactionAddedListener.java b/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/listener/EmoteTrackingReactionAddedListener.java new file mode 100644 index 000000000..ee7ec83ab --- /dev/null +++ b/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/listener/EmoteTrackingReactionAddedListener.java @@ -0,0 +1,65 @@ +package dev.sheldan.abstracto.statistic.emote.listener; + +import dev.sheldan.abstracto.core.config.FeatureDefinition; +import dev.sheldan.abstracto.core.config.FeatureMode; +import dev.sheldan.abstracto.core.listener.DefaultListenerResult; +import dev.sheldan.abstracto.core.listener.async.jda.AsyncReactionAddedListener; +import dev.sheldan.abstracto.core.models.listener.ReactionAddedModel; +import dev.sheldan.abstracto.core.service.EmoteService; +import dev.sheldan.abstracto.statistic.config.StatisticFeatureDefinition; +import dev.sheldan.abstracto.statistic.emote.config.EmoteTrackingMode; +import dev.sheldan.abstracto.statistic.emote.model.database.UsedEmoteType; +import dev.sheldan.abstracto.statistic.emote.service.RunTimeReactionEmotesService; +import dev.sheldan.abstracto.statistic.emote.service.TrackedEmoteService; +import java.time.Instant; +import java.util.Arrays; +import java.util.List; +import lombok.extern.slf4j.Slf4j; +import net.dv8tion.jda.api.entities.emoji.CustomEmoji; +import net.dv8tion.jda.api.entities.emoji.Emoji; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +@Slf4j +public class EmoteTrackingReactionAddedListener implements AsyncReactionAddedListener { + + @Autowired + private RunTimeReactionEmotesService runTimeReactionEmotesService; + + @Autowired + private TrackedEmoteService trackedEmoteService; + + @Autowired + private EmoteService emoteService; + + @Override + public DefaultListenerResult execute(ReactionAddedModel model) { + if(model.getReaction().getEmoji().getType() != Emoji.Type.CUSTOM) { + return DefaultListenerResult.IGNORED; + } + Long guildId = model.getServerId(); + Long messageId = model.getMessage().getMessageId(); + Long userId = model.getUserReacting().getUserId(); + CustomEmoji customEmoji = model.getReaction().getEmoji().asCustom(); + Long emoteId = customEmoji.getIdLong(); + if(runTimeReactionEmotesService.emoteAlreadyUsed(guildId, userId, messageId, emoteId)) { + return DefaultListenerResult.IGNORED; + } + log.debug("Counting usage of emote {} in server {}.", emoteId, guildId); + trackedEmoteService.addEmoteToRuntimeStorage(customEmoji, model.getReaction().getGuild(), 1L, UsedEmoteType.REACTION); + runTimeReactionEmotesService.getRuntimeEmotes().put(runTimeReactionEmotesService.getKeyFormat(guildId, userId, messageId, emoteId), Instant.now()); + return DefaultListenerResult.PROCESSED; + } + + @Override + public List getFeatureModeLimitations() { + return Arrays.asList(EmoteTrackingMode.TRACK_REACTIONS); + } + + @Override + public FeatureDefinition getFeature() { + return StatisticFeatureDefinition.EMOTE_TRACKING; + } + +} diff --git a/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/repository/UsedEmoteRepository.java b/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/repository/UsedEmoteRepository.java index 6cd30fa07..1c48205ab 100644 --- a/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/repository/UsedEmoteRepository.java +++ b/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/repository/UsedEmoteRepository.java @@ -24,12 +24,14 @@ public interface UsedEmoteRepository extends JpaRepository findEmoteFromServerToday(@Param("emote_id") Long emoteId, @Param("server_id") Long server_id); + "and use_date = date_trunc('day', now()) " + + " and type = :used_emote_type", nativeQuery = true) + Optional findEmoteFromServerToday(@Param("emote_id") Long emoteId, @Param("server_id") Long server_id, @Param("used_emote_type") String usedEmoteType); @Query(value = "select us.emote_id as emoteId, us.server_id as serverId, sum(us.amount) as amount from used_emote us " + "inner join tracked_emote te " + @@ -47,6 +49,17 @@ public interface UsedEmoteRepository extends JpaRepository getExternalEmoteStatsForServerSince(@Param("server_id") Long serverId, @Param("start_date") Instant since); + @Query(value = "select us.emote_id as emoteId, us.server_id as serverId, sum(us.amount) as amount from used_emote us " + + "inner join tracked_emote te " + + "on us.emote_id = te.id and us.server_id = te.server_id " + + "where us.use_date >= date_trunc('day', cast(:start_date AS timestamp)) " + + "and us.server_id = :server_id " + + "and te.external = true " + + "and us.type = :used_emote_type " + + "group by us.emote_id, us.server_id " + + "order by amount desc", nativeQuery = true) + List getExternalEmoteStatsForServerSince(@Param("server_id") Long serverId, @Param("start_date") Instant since, @Param("used_emote_type") String usedEmoteType); + @Query(value = "select us.emote_id as emoteId, us.server_id as serverId, sum(us.amount) as amount from used_emote us " + "inner join tracked_emote te " + "on us.emote_id = te.id and us.server_id = te.server_id " + @@ -55,6 +68,17 @@ public interface UsedEmoteRepository extends JpaRepository getDeletedEmoteStatsForServerSince(@Param("server_id") Long serverId, @Param("start_date") Instant since); + @Query(value = "select us.emote_id as emoteId, us.server_id as serverId, sum(us.amount) as amount from used_emote us " + + "inner join tracked_emote te " + + "on us.emote_id = te.id and us.server_id = te.server_id " + + "where us.use_date >= date_trunc('day', cast(:start_date AS timestamp)) " + + "and us.server_id = :server_id " + + "and te.deleted = true " + + "and us.type = :used_emote_type " + + "group by us.emote_id, us.server_id " + + "order by amount desc", nativeQuery = true) + List getDeletedEmoteStatsForServerSince(@Param("server_id") Long serverId, @Param("start_date") Instant since, @Param("used_emote_type") String usedEmoteType); + @Query(value = "select us.emote_id as emoteId, us.server_id as serverId, sum(us.amount) as amount from used_emote us " + "inner join tracked_emote te " + "on us.emote_id = te.id and us.server_id = te.server_id " + @@ -63,6 +87,18 @@ public interface UsedEmoteRepository extends JpaRepository getCurrentlyExistingEmoteStatsForServerSince(@Param("server_id") Long serverId, @Param("start_date") Instant since); + @Query(value = "select us.emote_id as emoteId, us.server_id as serverId, sum(us.amount) as amount from used_emote us " + + "inner join tracked_emote te " + + "on us.emote_id = te.id and us.server_id = te.server_id " + + "where us.use_date >= date_trunc('day', cast(:start_date AS timestamp)) " + + "and us.server_id = :server_id " + + "and te.external = false " + + "and te.deleted = false " + + "and us.type = :used_emote_type " + + "group by us.emote_id, us.server_id " + + "order by amount desc", nativeQuery = true) + List getCurrentlyExistingEmoteStatsForServerSince(@Param("server_id") Long serverId, @Param("start_date") Instant since, @Param("used_emote_type") String usedEmoteType); + @Query(value = "select :tracked_emote_id as emoteId, :server_id as serverId, sum(us.amount) as amount " + "from used_emote us " + "where us.use_date >= date_trunc('day', cast(:start_date AS timestamp)) " + @@ -70,6 +106,15 @@ public interface UsedEmoteRepository extends JpaRepository= date_trunc('day', cast(:start_date AS timestamp)) " + + "and us.server_id = :server_id " + + "and us.emote_id = :tracked_emote_id " + + "and us.type = :used_emote_type", nativeQuery = true) + EmoteStatsResult getEmoteStatForTrackedEmote(@Param("tracked_emote_id") Long trackedEmoteId, @Param("server_id") Long serverId, + @Param("start_date") Instant since, @Param("used_emote_type") String usedEmoteType); + void deleteByEmoteId_EmoteIdAndEmoteId_ServerIdAndEmoteId_UseDateGreaterThan(Long emoteId, Long serverId, Instant timestamp); List getByEmoteId_ServerIdAndEmoteId_UseDateGreaterThan(Long emoteId, Instant timestamp); diff --git a/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/service/RunTimeReactionEmotesService.java b/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/service/RunTimeReactionEmotesService.java new file mode 100644 index 000000000..af3a21daa --- /dev/null +++ b/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/service/RunTimeReactionEmotesService.java @@ -0,0 +1,41 @@ +package dev.sheldan.abstracto.statistic.emote.service; + +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +@Component +@Getter +@Slf4j +public class RunTimeReactionEmotesService { + + // key pattern "serverId:userId:messageId:emoteId" + private final Map runtimeEmotes = new ConcurrentHashMap<>(); + + public void cleanupRunTimeStorage() { + + Instant cutoffDate = Instant.now().minus(2, ChronoUnit.DAYS); + Set keysToRemove = new HashSet<>(); + runtimeEmotes.forEach((key, instant) -> { + if(instant.isBefore(cutoffDate)) { + keysToRemove.add(key); + } + }); + log.info("Cleaning up {} emote usages.", keysToRemove.size()); + keysToRemove.forEach(runtimeEmotes::remove); + } + + public boolean emoteAlreadyUsed(Long guildId, Long userId, Long messageId, Long emoteId) { + return runtimeEmotes.containsKey(getKeyFormat(guildId, userId, messageId, emoteId)); + } + + public String getKeyFormat(Long guildId, Long userId, Long messageId, Long emoteId) { + return String.format("%s:%s:%s:%s", guildId, userId, messageId, emoteId); + } +} \ No newline at end of file diff --git a/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/service/TrackedEmoteRuntimeServiceBean.java b/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/service/TrackedEmoteRuntimeServiceBean.java index 2ff481a5c..0ff62fd03 100644 --- a/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/service/TrackedEmoteRuntimeServiceBean.java +++ b/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/service/TrackedEmoteRuntimeServiceBean.java @@ -2,6 +2,7 @@ package dev.sheldan.abstracto.statistic.emote.service; import dev.sheldan.abstracto.core.models.cache.CachedEmote; import dev.sheldan.abstracto.statistic.emote.model.PersistingEmote; +import dev.sheldan.abstracto.statistic.emote.model.database.UsedEmoteType; import lombok.extern.slf4j.Slf4j; import net.dv8tion.jda.api.entities.Guild; import org.springframework.beans.factory.annotation.Autowired; @@ -25,34 +26,30 @@ public class TrackedEmoteRuntimeServiceBean implements TrackedEmoteRuntimeServic return trackedEmoteRunTimeStorage.getRuntimeConfig(); } - @Override - public void addEmoteForServer(CachedEmote emote, Guild guild, boolean external) { - addEmoteForServer(emote, guild, 1L, external); - } @Override - public void addEmoteForServer(CachedEmote emote, Guild guild, Long count, boolean external) { + public void addEmoteForServer(CachedEmote emote, Guild guild, Long count, boolean external, UsedEmoteType usedEmoteType) { takeLock(); try { // generate an appropriate key Long key = getKey(); // create a PersistingEmote based the given Emote - PersistingEmote newPersistentEmote = createFromEmote(guild, emote, count, external); + PersistingEmote newPersistentEmote = createFromEmote(guild, emote, count, external, usedEmoteType); if (trackedEmoteRunTimeStorage.contains(key)) { // if it already exists, we can add to the already existing map Map> elementsForKey = trackedEmoteRunTimeStorage.get(key); if (elementsForKey.containsKey(guild.getIdLong())) { // if the server already has an entry, we can just add it to the list of existing ones - List persistingEmotes = elementsForKey.get(guild.getIdLong()); - Optional existingEmote = persistingEmotes - .stream() - .filter(persistingEmote -> persistingEmote.getEmoteId().equals(emote.getEmoteId())) - .findFirst(); + List existingEmotes = elementsForKey.get(guild.getIdLong()); + Optional existingEmote = existingEmotes + .stream() + .filter(persistingEmote -> persistingEmote.getEmoteId().equals(emote.getEmoteId()) && persistingEmote.getUsedEmoteType().equals(usedEmoteType)) + .findFirst(); // if it exists already, just increment the counter by the given amount existingEmote.ifPresent(persistingEmote -> persistingEmote.setCount(persistingEmote.getCount() + count)); if (!existingEmote.isPresent()) { // just add the newly created one - persistingEmotes.add(newPersistentEmote); + existingEmotes.add(newPersistentEmote); } } else { // it did not exist for the server, create a new list of PersistingEmote @@ -77,12 +74,12 @@ public class TrackedEmoteRuntimeServiceBean implements TrackedEmoteRuntimeServic } @Override - public PersistingEmote createFromEmote(Guild guild, CachedEmote emote, boolean external) { - return createFromEmote(guild, emote, 1L, external); + public PersistingEmote createFromEmote(Guild guild, CachedEmote emote, boolean external, UsedEmoteType usedEmoteType) { + return createFromEmote(guild, emote, 1L, external, usedEmoteType); } @Override - public PersistingEmote createFromEmote(Guild guild, CachedEmote emote, Long count, boolean external) { + public PersistingEmote createFromEmote(Guild guild, CachedEmote emote, Long count, boolean external, UsedEmoteType usedEmoteType) { String url = external ? emote.getImageURL() : null; return PersistingEmote .builder() @@ -90,6 +87,7 @@ public class TrackedEmoteRuntimeServiceBean implements TrackedEmoteRuntimeServic .emoteId(emote.getEmoteId()) .external(external) .externalUrl(url) + .usedEmoteType(usedEmoteType) .emoteName(emote.getEmoteName()) .count(count) .serverId(guild.getIdLong()) diff --git a/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/service/TrackedEmoteServiceBean.java b/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/service/TrackedEmoteServiceBean.java index cc80ec3b1..45e82a1cb 100644 --- a/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/service/TrackedEmoteServiceBean.java +++ b/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/service/TrackedEmoteServiceBean.java @@ -15,6 +15,7 @@ import dev.sheldan.abstracto.statistic.emote.model.TrackedEmoteOverview; import dev.sheldan.abstracto.statistic.emote.model.TrackedEmoteSynchronizationResult; import dev.sheldan.abstracto.statistic.emote.model.database.TrackedEmote; import dev.sheldan.abstracto.statistic.emote.model.database.UsedEmote; +import dev.sheldan.abstracto.statistic.emote.model.database.UsedEmoteType; import dev.sheldan.abstracto.statistic.emote.service.management.TrackedEmoteManagementService; import dev.sheldan.abstracto.statistic.emote.service.management.UsedEmoteManagementService; import lombok.extern.slf4j.Slf4j; @@ -68,28 +69,29 @@ public class TrackedEmoteServiceBean implements TrackedEmoteService { @Override - public void addEmoteToRuntimeStorage(List emotes, Guild guild) { + public void addEmoteToRuntimeStorage(List emotes, Guild guild, UsedEmoteType type) { boolean externalTrackingEnabled = featureModeService.featureModeActive(StatisticFeatureDefinition.EMOTE_TRACKING, guild.getIdLong(), EmoteTrackingMode.EXTERNAL_EMOTES); emotes.forEach(emote -> { // either the emote is from the current guild (we always add those) or external emote tracking is enabled (we should always add those) if(externalTrackingEnabled || !emote.getExternal()) { - trackedEmoteRuntimeService.addEmoteForServer(emote, guild, emote.getExternal()); + trackedEmoteRuntimeService.addEmoteForServer(emote, guild, 1L, emote.getExternal(), type); } }); } + @Override - public void addEmoteToRuntimeStorage(CachedEmote emote, Guild guild, Long count) { + public void addEmoteToRuntimeStorage(CachedEmote emote, Guild guild, Long count, UsedEmoteType type) { boolean externalTrackingEnabled = featureModeService.featureModeActive(StatisticFeatureDefinition.EMOTE_TRACKING, guild.getIdLong(), EmoteTrackingMode.EXTERNAL_EMOTES); // either the emote is from the current guild (we always add those) or external emote tracking is enabled (we should always add those) if(externalTrackingEnabled || !emote.getExternal()) { - trackedEmoteRuntimeService.addEmoteForServer(emote, guild, count, emote.getExternal()); + trackedEmoteRuntimeService.addEmoteForServer(emote, guild, count, emote.getExternal(), type); } } @Override - public void addEmoteToRuntimeStorage(CustomEmoji emote, Guild guild, Long count) { - addEmoteToRuntimeStorage(cacheEntityService.getCachedEmoteFromEmote(emote, guild), guild, count); + public void addEmoteToRuntimeStorage(CustomEmoji emote, Guild guild, Long count, UsedEmoteType type) { + addEmoteToRuntimeStorage(cacheEntityService.getCachedEmoteFromEmote(emote, guild), guild, count, type); } @Override @@ -105,23 +107,23 @@ public class TrackedEmoteServiceBean implements TrackedEmoteService { emoteOptional.ifPresent(trackedEmote -> { // only track the record, if its enabled if(trackedEmote.getTrackingEnabled()) { - Optional existingUsedEmote = usedEmoteManagementService.loadUsedEmoteForTrackedEmoteToday(trackedEmote); + Optional existingUsedEmote = usedEmoteManagementService.loadUsedEmoteForTrackedEmoteToday(trackedEmote, persistingEmote.getUsedEmoteType()); // if a use for today already exists, increment the amount existingUsedEmote.ifPresent(usedEmote -> usedEmote.setAmount(usedEmote.getAmount() + persistingEmote.getCount()) ); // if none exists, create a new - if(!existingUsedEmote.isPresent()) { - usedEmoteManagementService.createEmoteUsageForToday(trackedEmote, persistingEmote.getCount()); + if(existingUsedEmote.isEmpty()) { + usedEmoteManagementService.createEmoteUsageForToday(trackedEmote, persistingEmote.getCount(), persistingEmote.getUsedEmoteType()); } } else { log.debug("Tracking disabled for emote {} in server {}.", trackedEmote.getTrackedEmoteId().getId(), trackedEmote.getTrackedEmoteId().getServerId()); } }); - // if tracked emote does not exists, we might want to create one (only for external emotes) + // if tracked emote does not exist, we might want to create one (only for external emotes) // we only do it for external emotes, because the feature mode AUTO_TRACK would not make sense // we might want emotes which are completely ignored by emote tracking - if(!emoteOptional.isPresent() && autoTrackExternalEmotes && trackExternalEmotes) { + if(emoteOptional.isEmpty() && autoTrackExternalEmotes && trackExternalEmotes) { createNewTrackedEmote(serverId, persistingEmote); } }); @@ -137,7 +139,7 @@ public class TrackedEmoteServiceBean implements TrackedEmoteService { Optional guildOptional = guildService.getGuildByIdOptional(serverId); guildOptional.ifPresent(guild -> { TrackedEmote newCreatedTrackedEmote = trackedEmoteManagementService.createExternalTrackedEmote(persistingEmote); - usedEmoteManagementService.createEmoteUsageForToday(newCreatedTrackedEmote, persistingEmote.getCount()); + usedEmoteManagementService.createEmoteUsageForToday(newCreatedTrackedEmote, persistingEmote.getCount(), persistingEmote.getUsedEmoteType()); }); } diff --git a/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/service/UsedEmoteServiceBean.java b/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/service/UsedEmoteServiceBean.java index 855794b10..45a817952 100644 --- a/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/service/UsedEmoteServiceBean.java +++ b/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/service/UsedEmoteServiceBean.java @@ -6,6 +6,7 @@ import dev.sheldan.abstracto.statistic.emote.model.EmoteStatsModel; import dev.sheldan.abstracto.statistic.emote.model.EmoteStatsResult; import dev.sheldan.abstracto.statistic.emote.model.EmoteStatsResultDisplay; import dev.sheldan.abstracto.statistic.emote.model.database.TrackedEmote; +import dev.sheldan.abstracto.statistic.emote.model.database.UsedEmoteType; import dev.sheldan.abstracto.statistic.emote.service.management.UsedEmoteManagementService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; @@ -32,26 +33,26 @@ public class UsedEmoteServiceBean implements UsedEmoteService { } @Override - public EmoteStatsModel getDeletedEmoteStatsForServerSince(AServer server, Instant since) { - List emoteStatsResults = usedEmoteManagementService.loadDeletedEmoteStatsForServerSince(server, since); + public EmoteStatsModel getDeletedEmoteStatsForServerSince(AServer server, Instant since, UsedEmoteType usedEmoteType) { + List emoteStatsResults = usedEmoteManagementService.loadDeletedEmoteStatsForServerSince(server, since, usedEmoteType); return converter.fromEmoteStatsResults(emoteStatsResults); } @Override - public EmoteStatsModel getExternalEmoteStatsForServerSince(AServer server, Instant since) { - List emoteStatsResults = usedEmoteManagementService.loadExternalEmoteStatsForServerSince(server, since); + public EmoteStatsModel getExternalEmoteStatsForServerSince(AServer server, Instant since, UsedEmoteType type) { + List emoteStatsResults = usedEmoteManagementService.loadExternalEmoteStatsForServerSince(server, since, type); return converter.fromEmoteStatsResults(emoteStatsResults); } @Override - public EmoteStatsModel getActiveEmoteStatsForServerSince(AServer server, Instant since) { - List emoteStatsResults = usedEmoteManagementService.loadActiveEmoteStatsForServerSince(server, since); + public EmoteStatsModel getActiveEmoteStatsForServerSince(AServer server, Instant since, UsedEmoteType usedEmoteType) { + List emoteStatsResults = usedEmoteManagementService.loadActiveEmoteStatsForServerSince(server, since, usedEmoteType); return converter.fromEmoteStatsResults(emoteStatsResults); } @Override - public EmoteStatsResultDisplay getEmoteStatForEmote(TrackedEmote trackedEmote, Instant since) { - EmoteStatsResult emoteStatsResult = usedEmoteManagementService.loadEmoteStatForEmote(trackedEmote, since); + public EmoteStatsResultDisplay getEmoteStatForEmote(TrackedEmote trackedEmote, Instant since, UsedEmoteType usedEmoteType) { + EmoteStatsResult emoteStatsResult = usedEmoteManagementService.loadEmoteStatForEmote(trackedEmote, since, usedEmoteType); return converter.convertEmoteStatsResultToDisplay(emoteStatsResult); } diff --git a/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/service/management/UsedEmoteManagementServiceBean.java b/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/service/management/UsedEmoteManagementServiceBean.java index 7c68edfb7..4af6fe826 100644 --- a/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/service/management/UsedEmoteManagementServiceBean.java +++ b/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/java/dev/sheldan/abstracto/statistic/emote/service/management/UsedEmoteManagementServiceBean.java @@ -4,6 +4,7 @@ import dev.sheldan.abstracto.core.models.database.AServer; import dev.sheldan.abstracto.statistic.emote.model.EmoteStatsResult; import dev.sheldan.abstracto.statistic.emote.model.database.TrackedEmote; import dev.sheldan.abstracto.statistic.emote.model.database.UsedEmote; +import dev.sheldan.abstracto.statistic.emote.model.database.UsedEmoteType; import dev.sheldan.abstracto.statistic.emote.model.database.embed.UsedEmoteDay; import dev.sheldan.abstracto.statistic.emote.repository.UsedEmoteRepository; import lombok.extern.slf4j.Slf4j; @@ -22,20 +23,20 @@ public class UsedEmoteManagementServiceBean implements UsedEmoteManagementServic private UsedEmoteRepository usedEmoteRepository; @Override - public Optional loadUsedEmoteForTrackedEmoteToday(TrackedEmote trackedEmote) { - return usedEmoteRepository.findEmoteFromServerToday(trackedEmote.getTrackedEmoteId().getId(), trackedEmote.getTrackedEmoteId().getServerId()); + public Optional loadUsedEmoteForTrackedEmoteToday(TrackedEmote trackedEmote, UsedEmoteType usedEmoteType) { + return usedEmoteRepository.findEmoteFromServerToday(trackedEmote.getTrackedEmoteId().getId(), trackedEmote.getTrackedEmoteId().getServerId(), usedEmoteType.name()); } @Override - public UsedEmote createEmoteUsageForToday(TrackedEmote trackedEmote, Long count) { - return createEmoteUsageFor(trackedEmote, count, Instant.now()); + public UsedEmote createEmoteUsageForToday(TrackedEmote trackedEmote, Long count, UsedEmoteType type) { + return createEmoteUsageFor(trackedEmote, count, Instant.now(), type); } @Override - public UsedEmote createEmoteUsageFor(TrackedEmote trackedEmote, Long count, Instant instant) { + public UsedEmote createEmoteUsageFor(TrackedEmote trackedEmote, Long count, Instant instant, UsedEmoteType type) { UsedEmote usedEmote = UsedEmote .builder() - .emoteId(new UsedEmoteDay(trackedEmote.getTrackedEmoteId().getId(), trackedEmote.getTrackedEmoteId().getServerId(), instant)) + .emoteId(new UsedEmoteDay(trackedEmote.getTrackedEmoteId().getId(), trackedEmote.getTrackedEmoteId().getServerId(), instant, type)) .amount(count) .build(); log.debug("Creating emote usage for emote {} in server {} with count {}.", trackedEmote.getTrackedEmoteId().getId(), trackedEmote.getTrackedEmoteId().getServerId(), count); @@ -53,23 +54,39 @@ public class UsedEmoteManagementServiceBean implements UsedEmoteManagementServic } @Override - public List loadDeletedEmoteStatsForServerSince(AServer server, Instant since) { - return usedEmoteRepository.getDeletedEmoteStatsForServerSince(server.getId(), since); + public List loadDeletedEmoteStatsForServerSince(AServer server, Instant since, UsedEmoteType usedEmoteType) { + if (usedEmoteType == null) { + return usedEmoteRepository.getDeletedEmoteStatsForServerSince(server.getId(), since); + } else { + return usedEmoteRepository.getDeletedEmoteStatsForServerSince(server.getId(), since, usedEmoteType.name()); + } } @Override - public List loadExternalEmoteStatsForServerSince(AServer server, Instant since) { - return usedEmoteRepository.getExternalEmoteStatsForServerSince(server.getId(), since); + public List loadExternalEmoteStatsForServerSince(AServer server, Instant since, UsedEmoteType type) { + if (type == null) { + return usedEmoteRepository.getExternalEmoteStatsForServerSince(server.getId(), since); + } else { + return usedEmoteRepository.getExternalEmoteStatsForServerSince(server.getId(), since, type.name()); + } } @Override - public List loadActiveEmoteStatsForServerSince(AServer server, Instant since) { - return usedEmoteRepository.getCurrentlyExistingEmoteStatsForServerSince(server.getId(), since); + public List loadActiveEmoteStatsForServerSince(AServer server, Instant since, UsedEmoteType usedEmoteType) { + if(usedEmoteType == null) { + return usedEmoteRepository.getCurrentlyExistingEmoteStatsForServerSince(server.getId(), since); + } else { + return usedEmoteRepository.getCurrentlyExistingEmoteStatsForServerSince(server.getId(), since, usedEmoteType.name()); + } } @Override - public EmoteStatsResult loadEmoteStatForEmote(TrackedEmote trackedEmote, Instant since) { - return usedEmoteRepository.getEmoteStatForTrackedEmote(trackedEmote.getTrackedEmoteId().getId(), trackedEmote.getTrackedEmoteId().getServerId(), since); + public EmoteStatsResult loadEmoteStatForEmote(TrackedEmote trackedEmote, Instant since, UsedEmoteType usedEmoteType) { + if(usedEmoteType == null) { + return usedEmoteRepository.getEmoteStatForTrackedEmote(trackedEmote.getTrackedEmoteId().getId(), trackedEmote.getTrackedEmoteId().getServerId(), since); + } else { + return usedEmoteRepository.getEmoteStatForTrackedEmote(trackedEmote.getTrackedEmoteId().getId(), trackedEmote.getTrackedEmoteId().getServerId(), since, usedEmoteType.name()); + } } @Override diff --git a/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/resources/migrations/1.5.58/collection.xml b/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/resources/migrations/1.5.58/collection.xml new file mode 100644 index 000000000..9b55885aa --- /dev/null +++ b/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/resources/migrations/1.5.58/collection.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/resources/migrations/1.5.58/seedData/data.xml b/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/resources/migrations/1.5.58/seedData/data.xml new file mode 100644 index 000000000..d803c81d8 --- /dev/null +++ b/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/resources/migrations/1.5.58/seedData/data.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/resources/migrations/1.5.58/seedData/emote_statistic_cleanup_job.xml b/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/resources/migrations/1.5.58/seedData/emote_statistic_cleanup_job.xml new file mode 100644 index 000000000..c400df0de --- /dev/null +++ b/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/resources/migrations/1.5.58/seedData/emote_statistic_cleanup_job.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/resources/migrations/1.5.58/tables/tables.xml b/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/resources/migrations/1.5.58/tables/tables.xml new file mode 100644 index 000000000..ff2190d69 --- /dev/null +++ b/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/resources/migrations/1.5.58/tables/tables.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/resources/migrations/1.5.58/tables/used_emote.xml b/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/resources/migrations/1.5.58/tables/used_emote.xml new file mode 100644 index 000000000..47d8c9724 --- /dev/null +++ b/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/resources/migrations/1.5.58/tables/used_emote.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/resources/migrations/statistic-changeLog.xml b/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/resources/migrations/statistic-changeLog.xml index 3c6e788d8..5944919a9 100644 --- a/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/resources/migrations/statistic-changeLog.xml +++ b/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/resources/migrations/statistic-changeLog.xml @@ -4,4 +4,5 @@ xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.26.xsd" > + \ No newline at end of file diff --git a/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/resources/statistic-config.properties b/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/resources/statistic-config.properties index 8f9da7deb..cb5ace6a1 100644 --- a/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/resources/statistic-config.properties +++ b/abstracto-application/abstracto-modules/statistic/statistic-impl/src/main/resources/statistic-config.properties @@ -12,4 +12,8 @@ abstracto.featureModes.externalEmotes.enabled=false abstracto.featureModes.autoTrackExternal.featureName=emoteTracking abstracto.featureModes.autoTrackExternal.mode=autoTrackExternal -abstracto.featureModes.autoTrackExternal.enabled=false \ No newline at end of file +abstracto.featureModes.autoTrackExternal.enabled=false + +abstracto.featureModes.trackReactions.featureName=emoteTracking +abstracto.featureModes.trackReactions.mode=trackReactions +abstracto.featureModes.trackReactions.enabled=false \ No newline at end of file diff --git a/abstracto-application/abstracto-modules/statistic/statistic-impl/src/test/java/dev/sheldan/abstracto/statistic/emote/command/DeletedEmoteStatsTest.java b/abstracto-application/abstracto-modules/statistic/statistic-impl/src/test/java/dev/sheldan/abstracto/statistic/emote/command/DeletedEmoteStatsTest.java index 822809372..c9d7ed00e 100644 --- a/abstracto-application/abstracto-modules/statistic/statistic-impl/src/test/java/dev/sheldan/abstracto/statistic/emote/command/DeletedEmoteStatsTest.java +++ b/abstracto-application/abstracto-modules/statistic/statistic-impl/src/test/java/dev/sheldan/abstracto/statistic/emote/command/DeletedEmoteStatsTest.java @@ -25,6 +25,7 @@ import java.util.Arrays; import static dev.sheldan.abstracto.statistic.emote.command.DeletedEmoteStats.EMOTE_STATS_ANIMATED_DELETED_RESPONSE; import static dev.sheldan.abstracto.statistic.emote.command.DeletedEmoteStats.EMOTE_STATS_STATIC_DELETED_RESPONSE; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.endsWith; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.when; @@ -54,7 +55,7 @@ public class DeletedEmoteStatsTest { when(model.getStaticEmotes()).thenReturn(Arrays.asList(display)); when(model.areStatsAvailable()).thenReturn(true); when(serverManagementService.loadServer(noParameters.getGuild())).thenReturn(server); - when(usedEmoteService.getDeletedEmoteStatsForServerSince(server, Instant.EPOCH)).thenReturn(model); + when(usedEmoteService.getDeletedEmoteStatsForServerSince(server, Instant.EPOCH, null)).thenReturn(model); when(channelService.sendEmbedTemplateInMessageChannel(EMOTE_STATS_STATIC_DELETED_RESPONSE, model, noParameters.getChannel())).thenReturn(CommandTestUtilities.messageFutureList()); CommandTestUtilities.checkSuccessfulCompletionAsync(testUnit.executeAsync(noParameters)); } @@ -67,7 +68,7 @@ public class DeletedEmoteStatsTest { when(model.getAnimatedEmotes()).thenReturn(Arrays.asList(display)); when(model.areStatsAvailable()).thenReturn(true); when(serverManagementService.loadServer(noParameters.getGuild())).thenReturn(server); - when(usedEmoteService.getDeletedEmoteStatsForServerSince(server, Instant.EPOCH)).thenReturn(model); + when(usedEmoteService.getDeletedEmoteStatsForServerSince(server, Instant.EPOCH, null)).thenReturn(model); when(channelService.sendEmbedTemplateInMessageChannel(EMOTE_STATS_ANIMATED_DELETED_RESPONSE, model, noParameters.getChannel())).thenReturn(CommandTestUtilities.messageFutureList()); CommandTestUtilities.checkSuccessfulCompletionAsync(testUnit.executeAsync(noParameters)); } @@ -78,7 +79,7 @@ public class DeletedEmoteStatsTest { EmoteStatsModel model = Mockito.mock(EmoteStatsModel.class); when(model.areStatsAvailable()).thenReturn(false); when(serverManagementService.loadServer(noParameters.getGuild())).thenReturn(server); - when(usedEmoteService.getDeletedEmoteStatsForServerSince(server, Instant.EPOCH)).thenReturn(model); + when(usedEmoteService.getDeletedEmoteStatsForServerSince(server, Instant.EPOCH, null)).thenReturn(model); when(channelService.sendEmbedTemplateInMessageChannel(eq(EmoteStats.EMOTE_STATS_NO_STATS_AVAILABLE), any(), eq(noParameters.getChannel()))).thenReturn(CommandTestUtilities.messageFutureList()); CommandTestUtilities.checkSuccessfulCompletionAsync(testUnit.executeAsync(noParameters)); } @@ -91,7 +92,7 @@ public class DeletedEmoteStatsTest { when(model.getStaticEmotes()).thenReturn(Arrays.asList(display)); when(model.areStatsAvailable()).thenReturn(true); when(serverManagementService.loadServer(noParameters.getGuild())).thenReturn(server); - when(usedEmoteService.getDeletedEmoteStatsForServerSince(eq(server), any(Instant.class))).thenReturn(model); + when(usedEmoteService.getDeletedEmoteStatsForServerSince(eq(server), any(Instant.class), eq(null))).thenReturn(model); when(channelService.sendEmbedTemplateInMessageChannel(EMOTE_STATS_STATIC_DELETED_RESPONSE, model, noParameters.getChannel())).thenReturn(CommandTestUtilities.messageFutureList()); CommandTestUtilities.checkSuccessfulCompletionAsync(testUnit.executeAsync(noParameters)); } diff --git a/abstracto-application/abstracto-modules/statistic/statistic-impl/src/test/java/dev/sheldan/abstracto/statistic/emote/command/EmoteStatsTest.java b/abstracto-application/abstracto-modules/statistic/statistic-impl/src/test/java/dev/sheldan/abstracto/statistic/emote/command/EmoteStatsTest.java index 01b575e1a..7a5761608 100644 --- a/abstracto-application/abstracto-modules/statistic/statistic-impl/src/test/java/dev/sheldan/abstracto/statistic/emote/command/EmoteStatsTest.java +++ b/abstracto-application/abstracto-modules/statistic/statistic-impl/src/test/java/dev/sheldan/abstracto/statistic/emote/command/EmoteStatsTest.java @@ -9,6 +9,7 @@ import dev.sheldan.abstracto.core.test.command.CommandTestUtilities; import dev.sheldan.abstracto.statistic.config.StatisticFeatureDefinition; import dev.sheldan.abstracto.statistic.emote.model.EmoteStatsModel; import dev.sheldan.abstracto.statistic.emote.model.EmoteStatsResultDisplay; +import dev.sheldan.abstracto.statistic.emote.model.database.UsedEmoteType; import dev.sheldan.abstracto.statistic.emote.service.UsedEmoteService; import org.junit.Assert; import org.junit.Test; @@ -54,7 +55,7 @@ public class EmoteStatsTest { when(model.getStaticEmotes()).thenReturn(Arrays.asList(display)); when(model.areStatsAvailable()).thenReturn(true); when(serverManagementService.loadServer(noParameters.getGuild())).thenReturn(server); - when(usedEmoteService.getActiveEmoteStatsForServerSince(server, Instant.EPOCH)).thenReturn(model); + when(usedEmoteService.getActiveEmoteStatsForServerSince(server, Instant.EPOCH, UsedEmoteType.REACTION)).thenReturn(model); when(channelService.sendEmbedTemplateInMessageChannel(EMOTE_STATS_STATIC_RESPONSE, model, noParameters.getChannel())).thenReturn(CommandTestUtilities.messageFutureList()); CommandTestUtilities.checkSuccessfulCompletionAsync(testUnit.executeAsync(noParameters)); } @@ -67,7 +68,7 @@ public class EmoteStatsTest { when(model.getAnimatedEmotes()).thenReturn(Arrays.asList(display)); when(model.areStatsAvailable()).thenReturn(true); when(serverManagementService.loadServer(noParameters.getGuild())).thenReturn(server); - when(usedEmoteService.getActiveEmoteStatsForServerSince(server, Instant.EPOCH)).thenReturn(model); + when(usedEmoteService.getActiveEmoteStatsForServerSince(server, Instant.EPOCH, null)).thenReturn(model); when(channelService.sendEmbedTemplateInMessageChannel(EMOTE_STATS_ANIMATED_RESPONSE, model, noParameters.getChannel())).thenReturn(CommandTestUtilities.messageFutureList()); CommandTestUtilities.checkSuccessfulCompletionAsync(testUnit.executeAsync(noParameters)); } @@ -78,7 +79,7 @@ public class EmoteStatsTest { EmoteStatsModel model = Mockito.mock(EmoteStatsModel.class); when(model.areStatsAvailable()).thenReturn(false); when(serverManagementService.loadServer(noParameters.getGuild())).thenReturn(server); - when(usedEmoteService.getActiveEmoteStatsForServerSince(server, Instant.EPOCH)).thenReturn(model); + when(usedEmoteService.getActiveEmoteStatsForServerSince(server, Instant.EPOCH, null)).thenReturn(model); when(channelService.sendEmbedTemplateInMessageChannel(eq(EmoteStats.EMOTE_STATS_NO_STATS_AVAILABLE), any(), eq(noParameters.getChannel()))).thenReturn(CommandTestUtilities.messageFutureList()); CommandTestUtilities.checkSuccessfulCompletionAsync(testUnit.executeAsync(noParameters)); } @@ -91,7 +92,7 @@ public class EmoteStatsTest { when(model.getStaticEmotes()).thenReturn(Arrays.asList(display)); when(model.areStatsAvailable()).thenReturn(true); when(serverManagementService.loadServer(noParameters.getGuild())).thenReturn(server); - when(usedEmoteService.getActiveEmoteStatsForServerSince(eq(server), any(Instant.class))).thenReturn(model); + when(usedEmoteService.getActiveEmoteStatsForServerSince(eq(server), any(Instant.class), eq(null))).thenReturn(model); when(channelService.sendEmbedTemplateInMessageChannel(EMOTE_STATS_STATIC_RESPONSE, model, noParameters.getChannel())).thenReturn(CommandTestUtilities.messageFutureList()); CommandTestUtilities.checkSuccessfulCompletionAsync(testUnit.executeAsync(noParameters)); } diff --git a/abstracto-application/abstracto-modules/statistic/statistic-impl/src/test/java/dev/sheldan/abstracto/statistic/emote/command/ExternalEmoteStatsTest.java b/abstracto-application/abstracto-modules/statistic/statistic-impl/src/test/java/dev/sheldan/abstracto/statistic/emote/command/ExternalEmoteStatsTest.java index a9fec6200..675581a58 100644 --- a/abstracto-application/abstracto-modules/statistic/statistic-impl/src/test/java/dev/sheldan/abstracto/statistic/emote/command/ExternalEmoteStatsTest.java +++ b/abstracto-application/abstracto-modules/statistic/statistic-impl/src/test/java/dev/sheldan/abstracto/statistic/emote/command/ExternalEmoteStatsTest.java @@ -54,7 +54,7 @@ public class ExternalEmoteStatsTest { when(model.getStaticEmotes()).thenReturn(Arrays.asList(display)); when(model.areStatsAvailable()).thenReturn(true); when(serverManagementService.loadServer(noParameters.getGuild())).thenReturn(server); - when(usedEmoteService.getExternalEmoteStatsForServerSince(server, Instant.EPOCH)).thenReturn(model); + when(usedEmoteService.getExternalEmoteStatsForServerSince(server, Instant.EPOCH, null)).thenReturn(model); when(channelService.sendEmbedTemplateInMessageChannel(EMOTE_STATS_STATIC_EXTERNAL_RESPONSE, model, noParameters.getChannel())).thenReturn(CommandTestUtilities.messageFutureList()); CommandTestUtilities.checkSuccessfulCompletionAsync(testUnit.executeAsync(noParameters)); } @@ -67,7 +67,7 @@ public class ExternalEmoteStatsTest { when(model.getAnimatedEmotes()).thenReturn(Arrays.asList(display)); when(model.areStatsAvailable()).thenReturn(true); when(serverManagementService.loadServer(noParameters.getGuild())).thenReturn(server); - when(usedEmoteService.getExternalEmoteStatsForServerSince(server, Instant.EPOCH)).thenReturn(model); + when(usedEmoteService.getExternalEmoteStatsForServerSince(server, Instant.EPOCH, null)).thenReturn(model); when(channelService.sendEmbedTemplateInMessageChannel(EMOTE_STATS_ANIMATED_EXTERNAL_RESPONSE, model, noParameters.getChannel())).thenReturn(CommandTestUtilities.messageFutureList()); CommandTestUtilities.checkSuccessfulCompletionAsync(testUnit.executeAsync(noParameters)); } @@ -78,7 +78,7 @@ public class ExternalEmoteStatsTest { EmoteStatsModel model = Mockito.mock(EmoteStatsModel.class); when(model.areStatsAvailable()).thenReturn(false); when(serverManagementService.loadServer(noParameters.getGuild())).thenReturn(server); - when(usedEmoteService.getExternalEmoteStatsForServerSince(server, Instant.EPOCH)).thenReturn(model); + when(usedEmoteService.getExternalEmoteStatsForServerSince(server, Instant.EPOCH, null)).thenReturn(model); when(channelService.sendEmbedTemplateInMessageChannel(eq(EmoteStats.EMOTE_STATS_NO_STATS_AVAILABLE), any(), eq(noParameters.getChannel()))).thenReturn(CommandTestUtilities.messageFutureList()); CommandTestUtilities.checkSuccessfulCompletionAsync(testUnit.executeAsync(noParameters)); } @@ -91,7 +91,7 @@ public class ExternalEmoteStatsTest { when(model.getStaticEmotes()).thenReturn(Arrays.asList(display)); when(model.areStatsAvailable()).thenReturn(true); when(serverManagementService.loadServer(noParameters.getGuild())).thenReturn(server); - when(usedEmoteService.getExternalEmoteStatsForServerSince(eq(server), any(Instant.class))).thenReturn(model); + when(usedEmoteService.getExternalEmoteStatsForServerSince(eq(server), any(Instant.class), eq(null))).thenReturn(model); when(channelService.sendEmbedTemplateInMessageChannel(EMOTE_STATS_STATIC_EXTERNAL_RESPONSE, model, noParameters.getChannel())).thenReturn(CommandTestUtilities.messageFutureList()); CommandTestUtilities.checkSuccessfulCompletionAsync(testUnit.executeAsync(noParameters)); } diff --git a/abstracto-application/abstracto-modules/statistic/statistic-impl/src/test/java/dev/sheldan/abstracto/statistic/emote/service/TrackedEmoteRuntimeServiceBeanTest.java b/abstracto-application/abstracto-modules/statistic/statistic-impl/src/test/java/dev/sheldan/abstracto/statistic/emote/service/TrackedEmoteRuntimeServiceBeanTest.java index ed0b352a8..5b1536fec 100644 --- a/abstracto-application/abstracto-modules/statistic/statistic-impl/src/test/java/dev/sheldan/abstracto/statistic/emote/service/TrackedEmoteRuntimeServiceBeanTest.java +++ b/abstracto-application/abstracto-modules/statistic/statistic-impl/src/test/java/dev/sheldan/abstracto/statistic/emote/service/TrackedEmoteRuntimeServiceBeanTest.java @@ -2,6 +2,7 @@ package dev.sheldan.abstracto.statistic.emote.service; import dev.sheldan.abstracto.core.models.cache.CachedEmote; import dev.sheldan.abstracto.statistic.emote.model.PersistingEmote; +import dev.sheldan.abstracto.statistic.emote.model.database.UsedEmoteType; import net.dv8tion.jda.api.entities.Guild; import org.junit.Assert; import org.junit.Test; @@ -47,7 +48,7 @@ public class TrackedEmoteRuntimeServiceBeanTest { @Test public void testCreateFromEmoteFromGuild() { when(emote.getEmoteId()).thenReturn(EMOTE_ID); - PersistingEmote createdEmote = testUnit.createFromEmote(guild, emote, COUNT, false); + PersistingEmote createdEmote = testUnit.createFromEmote(guild, emote, COUNT, false, UsedEmoteType.MESSAGE); Assert.assertFalse(createdEmote.getExternal()); Assert.assertNull(createdEmote.getExternalUrl()); Assert.assertEquals(EMOTE_ID, createdEmote.getEmoteId()); @@ -58,7 +59,7 @@ public class TrackedEmoteRuntimeServiceBeanTest { public void testCreateFromEmoteExternal() { when(emote.getImageURL()).thenReturn(URL); when(emote.getEmoteId()).thenReturn(EMOTE_ID); - PersistingEmote createdEmote = testUnit.createFromEmote(guild, emote, COUNT, true); + PersistingEmote createdEmote = testUnit.createFromEmote(guild, emote, COUNT, true, UsedEmoteType.MESSAGE); Assert.assertTrue(createdEmote.getExternal()); Assert.assertEquals(URL, createdEmote.getExternalUrl()); Assert.assertEquals(EMOTE_ID, createdEmote.getEmoteId()); @@ -68,7 +69,7 @@ public class TrackedEmoteRuntimeServiceBeanTest { @Test public void testCreateFromEmoteOneCountFromGuild() { when(emote.getEmoteId()).thenReturn(EMOTE_ID); - PersistingEmote createdEmote = testUnit.createFromEmote(guild, emote, false); + PersistingEmote createdEmote = testUnit.createFromEmote(guild, emote, false, UsedEmoteType.MESSAGE); Assert.assertFalse(createdEmote.getExternal()); Assert.assertNull(createdEmote.getExternalUrl()); Assert.assertEquals(EMOTE_ID, createdEmote.getEmoteId()); @@ -79,7 +80,7 @@ public class TrackedEmoteRuntimeServiceBeanTest { public void testCreateFromEmoteOneCountExternal() { when(emote.getImageURL()).thenReturn(URL); when(emote.getEmoteId()).thenReturn(EMOTE_ID); - PersistingEmote createdEmote = testUnit.createFromEmote(guild, emote, true); + PersistingEmote createdEmote = testUnit.createFromEmote(guild, emote, true, UsedEmoteType.MESSAGE); Assert.assertTrue(createdEmote.getExternal()); Assert.assertEquals(URL, createdEmote.getExternalUrl()); Assert.assertEquals(EMOTE_ID, createdEmote.getEmoteId()); @@ -92,7 +93,7 @@ public class TrackedEmoteRuntimeServiceBeanTest { when(emote.getEmoteId()).thenReturn(EMOTE_ID); when(guild.getIdLong()).thenReturn(SERVER_ID); when(trackedEmoteRunTimeStorage.contains(SECOND)).thenReturn(false); - testUnit.addEmoteForServer(emote, guild, false); + testUnit.addEmoteForServer(emote, guild, 1L, false, UsedEmoteType.REACTION); verify(trackedEmoteRunTimeStorage, times(1)).put(eq(SECOND), putCaptor.capture()); HashMap> value = putCaptor.getValue(); Assert.assertEquals(1, value.keySet().size()); @@ -110,7 +111,7 @@ public class TrackedEmoteRuntimeServiceBeanTest { when(trackedEmoteRunTimeStorage.contains(SECOND)).thenReturn(true); HashMap> serverMap = new HashMap<>(); when(trackedEmoteRunTimeStorage.get(SECOND)).thenReturn(serverMap); - testUnit.addEmoteForServer(emote, guild, false); + testUnit.addEmoteForServer(emote, guild, 1L, false, UsedEmoteType.REACTION); Assert.assertEquals(1, serverMap.keySet().size()); Assert.assertEquals(SERVER_ID, serverMap.keySet().iterator().next()); List createdEmotes = serverMap.values().iterator().next(); @@ -127,7 +128,7 @@ public class TrackedEmoteRuntimeServiceBeanTest { HashMap> serverMap = new HashMap<>(); serverMap.put(SERVER_ID, new ArrayList<>(Arrays.asList(persistingEmote))); when(trackedEmoteRunTimeStorage.get(SECOND)).thenReturn(serverMap); - testUnit.addEmoteForServer(emote, guild, false); + testUnit.addEmoteForServer(emote, guild, 1L,false, UsedEmoteType.REACTION); Assert.assertEquals(1, serverMap.keySet().size()); Assert.assertEquals(SERVER_ID, serverMap.keySet().iterator().next()); List persistingEmotes = serverMap.values().iterator().next(); @@ -147,7 +148,7 @@ public class TrackedEmoteRuntimeServiceBeanTest { when(persistingEmote.getCount()).thenReturn(COUNT); serverMap.put(SERVER_ID, new ArrayList<>(Arrays.asList(persistingEmote))); when(trackedEmoteRunTimeStorage.get(SECOND)).thenReturn(serverMap); - testUnit.addEmoteForServer(emote, guild, false); + testUnit.addEmoteForServer(emote, guild, 1L, false, UsedEmoteType.REACTION); Assert.assertEquals(1, serverMap.keySet().size()); Assert.assertEquals(SERVER_ID, serverMap.keySet().iterator().next()); List persistingEmotes = serverMap.values().iterator().next(); @@ -168,7 +169,7 @@ public class TrackedEmoteRuntimeServiceBeanTest { when(persistingEmote.getCount()).thenReturn(COUNT); serverMap.put(SERVER_ID, new ArrayList<>(Arrays.asList(persistingEmote))); when(trackedEmoteRunTimeStorage.get(SECOND)).thenReturn(serverMap); - testUnit.addEmoteForServer(emote, guild, COUNT, false); + testUnit.addEmoteForServer(emote, guild, COUNT, false, UsedEmoteType.REACTION); Assert.assertEquals(1, serverMap.keySet().size()); Assert.assertEquals(SERVER_ID, serverMap.keySet().iterator().next()); List persistingEmotes = serverMap.values().iterator().next(); diff --git a/abstracto-application/abstracto-modules/statistic/statistic-impl/src/test/java/dev/sheldan/abstracto/statistic/emote/service/TrackedEmoteServiceBeanTest.java b/abstracto-application/abstracto-modules/statistic/statistic-impl/src/test/java/dev/sheldan/abstracto/statistic/emote/service/TrackedEmoteServiceBeanTest.java index 840adf957..8833a5539 100644 --- a/abstracto-application/abstracto-modules/statistic/statistic-impl/src/test/java/dev/sheldan/abstracto/statistic/emote/service/TrackedEmoteServiceBeanTest.java +++ b/abstracto-application/abstracto-modules/statistic/statistic-impl/src/test/java/dev/sheldan/abstracto/statistic/emote/service/TrackedEmoteServiceBeanTest.java @@ -13,6 +13,7 @@ import dev.sheldan.abstracto.statistic.emote.model.TrackedEmoteOverview; import dev.sheldan.abstracto.statistic.emote.model.TrackedEmoteSynchronizationResult; import dev.sheldan.abstracto.statistic.emote.model.database.TrackedEmote; import dev.sheldan.abstracto.statistic.emote.model.database.UsedEmote; +import dev.sheldan.abstracto.statistic.emote.model.database.UsedEmoteType; import dev.sheldan.abstracto.statistic.emote.service.management.TrackedEmoteManagementService; import dev.sheldan.abstracto.statistic.emote.service.management.UsedEmoteManagementService; import net.dv8tion.jda.api.entities.Guild; @@ -109,40 +110,40 @@ public class TrackedEmoteServiceBeanTest { public void addSingleServerEmote() { externalEmotesEnabled(true); isEmoteExternal(false); - testUnit.addEmoteToRuntimeStorage(emote, guild, COUNT); - verify(trackedEmoteRuntimeService, times(1)).addEmoteForServer(emote, guild, COUNT,false); + testUnit.addEmoteToRuntimeStorage(emote, guild, COUNT, UsedEmoteType.REACTION); + verify(trackedEmoteRuntimeService, times(1)).addEmoteForServer(emote, guild, COUNT, false, UsedEmoteType.REACTION); } @Test public void addSingleExternalEmote() { externalEmotesEnabled(true); isEmoteExternal(true); - testUnit.addEmoteToRuntimeStorage(emote, guild, COUNT); - verify(trackedEmoteRuntimeService, times(1)).addEmoteForServer(emote, guild, COUNT,true); + testUnit.addEmoteToRuntimeStorage(emote, guild, COUNT, UsedEmoteType.REACTION); + verify(trackedEmoteRuntimeService, times(1)).addEmoteForServer(emote, guild, COUNT, true, UsedEmoteType.REACTION); } @Test public void addSingleExternalWhenExternalDisabled() { externalEmotesEnabled(false); isEmoteExternal(true); - testUnit.addEmoteToRuntimeStorage(emote, guild, COUNT); - verify(trackedEmoteRuntimeService, times(0)).addEmoteForServer(eq(emote), eq(guild), anyBoolean()); + testUnit.addEmoteToRuntimeStorage(emote, guild, COUNT, UsedEmoteType.REACTION); + verify(trackedEmoteRuntimeService, times(0)).addEmoteForServer(eq(emote), eq(guild), anyLong(), anyBoolean(), eq(UsedEmoteType.REACTION)); } @Test public void addSingleServerEmoteExternalDisabled() { externalEmotesEnabled(false); isEmoteExternal(false); - testUnit.addEmoteToRuntimeStorage(emote, guild, COUNT); - verify(trackedEmoteRuntimeService, times(1)).addEmoteForServer(emote, guild, COUNT, false); + testUnit.addEmoteToRuntimeStorage(emote, guild, COUNT, UsedEmoteType.REACTION); + verify(trackedEmoteRuntimeService, times(1)).addEmoteForServer(emote, guild, COUNT, false, UsedEmoteType.REACTION); } @Test public void addTwoExternalEmotes() { externalEmotesEnabled(true); bothEmotesExternal(true, true); - testUnit.addEmoteToRuntimeStorage(Arrays.asList(emote, secondEmote), guild); - verify(trackedEmoteRuntimeService, times(2)).addEmoteForServer(emoteArgumentCaptor.capture(), eq(guild), eq(true)); + testUnit.addEmoteToRuntimeStorage(Arrays.asList(emote, secondEmote), guild, UsedEmoteType.REACTION); + verify(trackedEmoteRuntimeService, times(2)).addEmoteForServer(emoteArgumentCaptor.capture(), eq(guild), eq(1L), eq(true), eq(UsedEmoteType.REACTION)); List usedEmotes = emoteArgumentCaptor.getAllValues(); Assert.assertEquals(2, usedEmotes.size()); Assert.assertEquals(emote, usedEmotes.get(0)); @@ -153,8 +154,8 @@ public class TrackedEmoteServiceBeanTest { public void addOneExternalAndOneLocalEmote() { externalEmotesEnabled(true); bothEmotesExternal(true, false); - testUnit.addEmoteToRuntimeStorage(Arrays.asList(emote, secondEmote), guild); - verify(trackedEmoteRuntimeService, times(2)).addEmoteForServer(emoteArgumentCaptor.capture(), eq(guild), booleanArgumentCaptor.capture()); + testUnit.addEmoteToRuntimeStorage(Arrays.asList(emote, secondEmote), guild, UsedEmoteType.REACTION); + verify(trackedEmoteRuntimeService, times(2)).addEmoteForServer(emoteArgumentCaptor.capture(), eq(guild), eq(1L), booleanArgumentCaptor.capture(), eq(UsedEmoteType.REACTION)); List usedEmotes = emoteArgumentCaptor.getAllValues(); Assert.assertEquals(2, usedEmotes.size()); Assert.assertEquals(emote, usedEmotes.get(0)); @@ -169,16 +170,16 @@ public class TrackedEmoteServiceBeanTest { public void addTwoExternalEmotesWhenExternalDisabled() { externalEmotesEnabled(false); bothEmotesExternal(true, true); - testUnit.addEmoteToRuntimeStorage(Arrays.asList(emote, secondEmote), guild); - verify(trackedEmoteRuntimeService, times(0)).addEmoteForServer(emoteArgumentCaptor.capture(), eq(guild), booleanArgumentCaptor.capture()); + testUnit.addEmoteToRuntimeStorage(Arrays.asList(emote, secondEmote), guild, UsedEmoteType.REACTION); + verify(trackedEmoteRuntimeService, times(0)).addEmoteForServer(emoteArgumentCaptor.capture(), eq(guild), eq(1L), booleanArgumentCaptor.capture(), eq(UsedEmoteType.REACTION)); } @Test public void addTwoLocalEmotes() { externalEmotesEnabled(false); bothEmotesExternal(false, false); - testUnit.addEmoteToRuntimeStorage(Arrays.asList(emote, secondEmote), guild); - verify(trackedEmoteRuntimeService, times(2)).addEmoteForServer(emoteArgumentCaptor.capture(), eq(guild), booleanArgumentCaptor.capture()); + testUnit.addEmoteToRuntimeStorage(Arrays.asList(emote, secondEmote), guild, UsedEmoteType.REACTION); + verify(trackedEmoteRuntimeService, times(2)).addEmoteForServer(emoteArgumentCaptor.capture(), eq(guild), eq(1L), booleanArgumentCaptor.capture(), eq(UsedEmoteType.REACTION)); List usedEmotes = emoteArgumentCaptor.getAllValues(); Assert.assertEquals(2, usedEmotes.size()); Assert.assertEquals(emote, usedEmotes.get(0)); @@ -313,7 +314,7 @@ public class TrackedEmoteServiceBeanTest { when(persistingEmote.getEmoteId()).thenReturn(EMOTE_ID); when(persistingEmote.getCount()).thenReturn(COUNT); when(trackedEmoteManagementService.loadByEmoteIdOptional(EMOTE_ID, SERVER_ID)).thenReturn(Optional.of(trackedEmote)); - when(usedEmoteManagementService.loadUsedEmoteForTrackedEmoteToday(trackedEmote)).thenReturn(Optional.of(usedEmote)); + when(usedEmoteManagementService.loadUsedEmoteForTrackedEmoteToday(trackedEmote, UsedEmoteType.REACTION)).thenReturn(Optional.of(usedEmote)); when(trackedEmote.getTrackingEnabled()).thenReturn(true); when(usedEmote.getAmount()).thenReturn(COUNT); when(featureModeService.featureModeActive(StatisticFeatureDefinition.EMOTE_TRACKING, SERVER_ID, EmoteTrackingMode.AUTO_TRACK_EXTERNAL)).thenReturn(true); @@ -344,13 +345,14 @@ public class TrackedEmoteServiceBeanTest { usagesToStore.put(SERVER_ID, Arrays.asList(persistingEmote)); when(persistingEmote.getEmoteId()).thenReturn(EMOTE_ID); when(persistingEmote.getCount()).thenReturn(COUNT); + when(persistingEmote.getUsedEmoteType()).thenReturn(UsedEmoteType.REACTION); when(trackedEmoteManagementService.loadByEmoteIdOptional(EMOTE_ID, SERVER_ID)).thenReturn(Optional.of(trackedEmote)); - when(usedEmoteManagementService.loadUsedEmoteForTrackedEmoteToday(trackedEmote)).thenReturn(Optional.empty()); + when(usedEmoteManagementService.loadUsedEmoteForTrackedEmoteToday(trackedEmote, UsedEmoteType.REACTION)).thenReturn(Optional.empty()); when(trackedEmote.getTrackingEnabled()).thenReturn(true); when(featureModeService.featureModeActive(StatisticFeatureDefinition.EMOTE_TRACKING, SERVER_ID, EmoteTrackingMode.AUTO_TRACK_EXTERNAL)).thenReturn(true); when(featureModeService.featureModeActive(StatisticFeatureDefinition.EMOTE_TRACKING, SERVER_ID, EmoteTrackingMode.EXTERNAL_EMOTES)).thenReturn(true); testUnit.storeEmoteStatistics(usagesToStore); - verify(usedEmoteManagementService, times(1)).createEmoteUsageForToday(trackedEmote, COUNT); + verify(usedEmoteManagementService, times(1)).createEmoteUsageForToday(trackedEmote, COUNT, UsedEmoteType.REACTION); verify(metricService, times(1)).incrementCounter(any()); } @@ -359,11 +361,12 @@ public class TrackedEmoteServiceBeanTest { HashMap> usagesToStore = new HashMap<>(); usagesToStore.put(SERVER_ID, Arrays.asList(persistingEmote)); when(persistingEmote.getEmoteId()).thenReturn(EMOTE_ID); + when(persistingEmote.getUsedEmoteType()).thenReturn(UsedEmoteType.REACTION); when(trackedEmoteManagementService.loadByEmoteIdOptional(EMOTE_ID, SERVER_ID)).thenReturn(Optional.empty()); when(featureModeService.featureModeActive(StatisticFeatureDefinition.EMOTE_TRACKING, SERVER_ID, EmoteTrackingMode.AUTO_TRACK_EXTERNAL)).thenReturn(true); when(featureModeService.featureModeActive(StatisticFeatureDefinition.EMOTE_TRACKING, SERVER_ID, EmoteTrackingMode.EXTERNAL_EMOTES)).thenReturn(true); testUnit.storeEmoteStatistics(usagesToStore); - verify(usedEmoteManagementService, times(0)).createEmoteUsageForToday(any(TrackedEmote.class), anyLong()); + verify(usedEmoteManagementService, times(0)).createEmoteUsageForToday(any(TrackedEmote.class), anyLong(), eq(UsedEmoteType.REACTION)); verify(metricService, times(1)).incrementCounter(any()); } @@ -372,11 +375,12 @@ public class TrackedEmoteServiceBeanTest { HashMap> usagesToStore = new HashMap<>(); usagesToStore.put(SERVER_ID, Arrays.asList(persistingEmote)); when(persistingEmote.getEmoteId()).thenReturn(EMOTE_ID); + when(persistingEmote.getUsedEmoteType()).thenReturn(UsedEmoteType.REACTION); when(trackedEmoteManagementService.loadByEmoteIdOptional(EMOTE_ID, SERVER_ID)).thenReturn(Optional.empty()); when(featureModeService.featureModeActive(StatisticFeatureDefinition.EMOTE_TRACKING, SERVER_ID, EmoteTrackingMode.AUTO_TRACK_EXTERNAL)).thenReturn(false); when(featureModeService.featureModeActive(StatisticFeatureDefinition.EMOTE_TRACKING, SERVER_ID, EmoteTrackingMode.EXTERNAL_EMOTES)).thenReturn(true); testUnit.storeEmoteStatistics(usagesToStore); - verify(usedEmoteManagementService, times(0)).createEmoteUsageForToday(any(TrackedEmote.class), anyLong()); + verify(usedEmoteManagementService, times(0)).createEmoteUsageForToday(any(TrackedEmote.class), anyLong(), eq(UsedEmoteType.REACTION)); verify(metricService, times(1)).incrementCounter(any()); } @@ -385,6 +389,7 @@ public class TrackedEmoteServiceBeanTest { HashMap> usagesToStore = new HashMap<>(); usagesToStore.put(SERVER_ID, Arrays.asList(persistingEmote)); when(persistingEmote.getEmoteId()).thenReturn(EMOTE_ID); + when(persistingEmote.getUsedEmoteType()).thenReturn(UsedEmoteType.REACTION); when(guildService.getGuildByIdOptional(SERVER_ID)).thenReturn(Optional.of(guild)); when(persistingEmote.getCount()).thenReturn(COUNT); when(trackedEmoteManagementService.loadByEmoteIdOptional(EMOTE_ID, SERVER_ID)).thenReturn(Optional.empty()); @@ -392,7 +397,7 @@ public class TrackedEmoteServiceBeanTest { when(featureModeService.featureModeActive(StatisticFeatureDefinition.EMOTE_TRACKING, SERVER_ID, EmoteTrackingMode.AUTO_TRACK_EXTERNAL)).thenReturn(true); when(featureModeService.featureModeActive(StatisticFeatureDefinition.EMOTE_TRACKING, SERVER_ID, EmoteTrackingMode.EXTERNAL_EMOTES)).thenReturn(true); testUnit.storeEmoteStatistics(usagesToStore); - verify(usedEmoteManagementService, times(1)).createEmoteUsageForToday(trackedEmote, COUNT); + verify(usedEmoteManagementService, times(1)).createEmoteUsageForToday(trackedEmote, COUNT, UsedEmoteType.REACTION); verify(metricService, times(1)).incrementCounter(any()); } @@ -405,7 +410,7 @@ public class TrackedEmoteServiceBeanTest { when(trackedEmoteManagementService.loadByEmoteIdOptional(EMOTE_ID, SERVER_ID)).thenReturn(Optional.of(trackedEmote)); when(persistingEmote.getEmoteId()).thenReturn(EMOTE_ID); when(persistingEmote.getCount()).thenReturn(COUNT); - when(usedEmoteManagementService.loadUsedEmoteForTrackedEmoteToday(trackedEmote)).thenReturn(Optional.of(usedEmote)); + when(usedEmoteManagementService.loadUsedEmoteForTrackedEmoteToday(trackedEmote, UsedEmoteType.REACTION)).thenReturn(Optional.of(usedEmote)); when(featureModeService.featureModeActive(StatisticFeatureDefinition.EMOTE_TRACKING, SERVER_ID, EmoteTrackingMode.AUTO_TRACK_EXTERNAL)).thenReturn(true); when(featureModeService.featureModeActive(StatisticFeatureDefinition.EMOTE_TRACKING, SERVER_ID, EmoteTrackingMode.EXTERNAL_EMOTES)).thenReturn(true); @@ -413,6 +418,7 @@ public class TrackedEmoteServiceBeanTest { usagesToStore.put(serverId2, Arrays.asList(persistingEmote2)); when(trackedEmoteManagementService.loadByEmoteIdOptional(EMOTE_ID_2, serverId2)).thenReturn(Optional.of(trackedEmote2)); when(trackedEmote2.getTrackingEnabled()).thenReturn(true); + when(persistingEmote2.getUsedEmoteType()).thenReturn(UsedEmoteType.REACTION); when(persistingEmote2.getEmoteId()).thenReturn(EMOTE_ID_2); when(persistingEmote2.getCount()).thenReturn(COUNT); when(usedEmote.getAmount()).thenReturn(COUNT); @@ -421,7 +427,7 @@ public class TrackedEmoteServiceBeanTest { testUnit.storeEmoteStatistics(usagesToStore); - verify(usedEmoteManagementService, times(1)).createEmoteUsageForToday(eq(trackedEmote2), anyLong()); + verify(usedEmoteManagementService, times(1)).createEmoteUsageForToday(eq(trackedEmote2), anyLong(), eq(UsedEmoteType.REACTION)); verify(usedEmote, times(1)).setAmount(2 * COUNT); verify(metricService, times(2)).incrementCounter(any()); } diff --git a/abstracto-application/abstracto-modules/statistic/statistic-impl/src/test/java/dev/sheldan/abstracto/statistic/emote/service/UsedEmoteServiceBeanTest.java b/abstracto-application/abstracto-modules/statistic/statistic-impl/src/test/java/dev/sheldan/abstracto/statistic/emote/service/UsedEmoteServiceBeanTest.java index 61ca9a7fc..8dc12c95d 100644 --- a/abstracto-application/abstracto-modules/statistic/statistic-impl/src/test/java/dev/sheldan/abstracto/statistic/emote/service/UsedEmoteServiceBeanTest.java +++ b/abstracto-application/abstracto-modules/statistic/statistic-impl/src/test/java/dev/sheldan/abstracto/statistic/emote/service/UsedEmoteServiceBeanTest.java @@ -6,6 +6,7 @@ import dev.sheldan.abstracto.statistic.emote.converter.EmoteStatsConverter; import dev.sheldan.abstracto.statistic.emote.model.EmoteStatsModel; import dev.sheldan.abstracto.statistic.emote.model.EmoteStatsResult; import dev.sheldan.abstracto.statistic.emote.model.database.TrackedEmote; +import dev.sheldan.abstracto.statistic.emote.model.database.UsedEmoteType; import dev.sheldan.abstracto.statistic.emote.service.management.UsedEmoteManagementService; import org.junit.Assert; import org.junit.Test; @@ -58,27 +59,27 @@ public class UsedEmoteServiceBeanTest { @Test public void testGetDeletedEmoteStatsForServerSince() { List mockedEmoteStatsResult = getMockedStatsResult(); - when(usedEmoteManagementService.loadDeletedEmoteStatsForServerSince(eq(server), any(Instant.class))).thenReturn(mockedEmoteStatsResult); + when(usedEmoteManagementService.loadDeletedEmoteStatsForServerSince(eq(server), any(Instant.class), eq(UsedEmoteType.REACTION))).thenReturn(mockedEmoteStatsResult); when(converter.fromEmoteStatsResults(mockedEmoteStatsResult)).thenReturn(emoteStatsModel); - EmoteStatsModel result = testUnit.getDeletedEmoteStatsForServerSince(server, pointInTime); + EmoteStatsModel result = testUnit.getDeletedEmoteStatsForServerSince(server, pointInTime, UsedEmoteType.REACTION); Assert.assertEquals(emoteStatsModel, result); } @Test public void testGetExternalEmoteStatsForServerSince() { List mockedEmoteStatsResult = getMockedStatsResult(); - when(usedEmoteManagementService.loadExternalEmoteStatsForServerSince(eq(server), any(Instant.class))).thenReturn(mockedEmoteStatsResult); + when(usedEmoteManagementService.loadExternalEmoteStatsForServerSince(eq(server), any(Instant.class), eq(UsedEmoteType.REACTION))).thenReturn(mockedEmoteStatsResult); when(converter.fromEmoteStatsResults(mockedEmoteStatsResult)).thenReturn(emoteStatsModel); - EmoteStatsModel result = testUnit.getExternalEmoteStatsForServerSince(server, pointInTime); + EmoteStatsModel result = testUnit.getExternalEmoteStatsForServerSince(server, pointInTime, UsedEmoteType.REACTION); Assert.assertEquals(emoteStatsModel, result); } @Test public void testGetActiveEmoteStatsForServerSince() { List mockedEmoteStatsResult = getMockedStatsResult(); - when(usedEmoteManagementService.loadActiveEmoteStatsForServerSince(eq(server), any(Instant.class))).thenReturn(mockedEmoteStatsResult); + when(usedEmoteManagementService.loadActiveEmoteStatsForServerSince(eq(server), any(Instant.class), eq(UsedEmoteType.REACTION))).thenReturn(mockedEmoteStatsResult); when(converter.fromEmoteStatsResults(mockedEmoteStatsResult)).thenReturn(emoteStatsModel); - EmoteStatsModel result = testUnit.getActiveEmoteStatsForServerSince(server, pointInTime); + EmoteStatsModel result = testUnit.getActiveEmoteStatsForServerSince(server, pointInTime, UsedEmoteType.REACTION); Assert.assertEquals(emoteStatsModel, result); } diff --git a/abstracto-application/abstracto-modules/statistic/statistic-impl/src/test/java/dev/sheldan/abstracto/statistic/emote/service/management/UsedEmoteManagementServiceBeanTest.java b/abstracto-application/abstracto-modules/statistic/statistic-impl/src/test/java/dev/sheldan/abstracto/statistic/emote/service/management/UsedEmoteManagementServiceBeanTest.java index 3ebf0e234..0e1ca7b90 100644 --- a/abstracto-application/abstracto-modules/statistic/statistic-impl/src/test/java/dev/sheldan/abstracto/statistic/emote/service/management/UsedEmoteManagementServiceBeanTest.java +++ b/abstracto-application/abstracto-modules/statistic/statistic-impl/src/test/java/dev/sheldan/abstracto/statistic/emote/service/management/UsedEmoteManagementServiceBeanTest.java @@ -5,6 +5,7 @@ import dev.sheldan.abstracto.core.models.database.AServer; import dev.sheldan.abstracto.statistic.emote.model.EmoteStatsResult; import dev.sheldan.abstracto.statistic.emote.model.database.TrackedEmote; import dev.sheldan.abstracto.statistic.emote.model.database.UsedEmote; +import dev.sheldan.abstracto.statistic.emote.model.database.UsedEmoteType; import dev.sheldan.abstracto.statistic.emote.repository.UsedEmoteRepository; import org.junit.Assert; import org.junit.Test; @@ -47,8 +48,8 @@ public class UsedEmoteManagementServiceBeanTest { @Test public void testLoadUsedEmoteForTrackedEmoteToday() { setupTrackedEmote(); - when(usedEmoteRepository.findEmoteFromServerToday(EMOTE_ID, SERVER_ID)).thenReturn(Optional.of(usedEmote)); - Optional usedEmoteOptional = testUnit.loadUsedEmoteForTrackedEmoteToday(trackedEmote); + when(usedEmoteRepository.findEmoteFromServerToday(EMOTE_ID, SERVER_ID, UsedEmoteType.REACTION.name())).thenReturn(Optional.of(usedEmote)); + Optional usedEmoteOptional = testUnit.loadUsedEmoteForTrackedEmoteToday(trackedEmote, UsedEmoteType.REACTION); Assert.assertTrue(usedEmoteOptional.isPresent()); usedEmoteOptional.ifPresent(usedEmote1 -> Assert.assertEquals(usedEmote, usedEmote1) @@ -58,12 +59,13 @@ public class UsedEmoteManagementServiceBeanTest { @Test public void testCreateEmoteUsageForToday() { setupTrackedEmote(); - testUnit.createEmoteUsageForToday(trackedEmote, COUNT); + testUnit.createEmoteUsageForToday(trackedEmote, COUNT, UsedEmoteType.REACTION); verify(usedEmoteRepository, times(1)).save(usedEmoteArgumentCaptor.capture()); UsedEmote createdUsedEmote = usedEmoteArgumentCaptor.getValue(); Assert.assertEquals(COUNT, createdUsedEmote.getAmount()); Assert.assertEquals(EMOTE_ID, createdUsedEmote.getEmoteId().getEmoteId()); Assert.assertEquals(SERVER_ID, createdUsedEmote.getEmoteId().getServerId()); + Assert.assertEquals(UsedEmoteType.REACTION, createdUsedEmote.getEmoteId().getType()); } @Test @@ -97,7 +99,7 @@ public class UsedEmoteManagementServiceBeanTest { setupServer(); List results = getEmoteStatsResults(); when(usedEmoteRepository.getDeletedEmoteStatsForServerSince(SERVER_ID, Instant.EPOCH)).thenReturn(results); - List returnedResult = testUnit.loadDeletedEmoteStatsForServerSince(server, Instant.EPOCH); + List returnedResult = testUnit.loadDeletedEmoteStatsForServerSince(server, Instant.EPOCH, UsedEmoteType.REACTION); Assert.assertEquals(results.size(), returnedResult.size()); Assert.assertEquals(results, returnedResult); } @@ -107,7 +109,7 @@ public class UsedEmoteManagementServiceBeanTest { setupServer(); List results = getEmoteStatsResults(); when(usedEmoteRepository.getExternalEmoteStatsForServerSince(SERVER_ID, Instant.EPOCH)).thenReturn(results); - List returnedResult = testUnit.loadExternalEmoteStatsForServerSince(server, Instant.EPOCH); + List returnedResult = testUnit.loadExternalEmoteStatsForServerSince(server, Instant.EPOCH, UsedEmoteType.REACTION); Assert.assertEquals(results.size(), returnedResult.size()); Assert.assertEquals(results, returnedResult); } @@ -116,8 +118,8 @@ public class UsedEmoteManagementServiceBeanTest { public void testLoadActiveEmoteStatsForServerSince() { setupServer(); List results = getEmoteStatsResults(); - when(usedEmoteRepository.getCurrentlyExistingEmoteStatsForServerSince(SERVER_ID, Instant.EPOCH)).thenReturn(results); - List returnedResult = testUnit.loadActiveEmoteStatsForServerSince(server, Instant.EPOCH); + when(usedEmoteRepository.getCurrentlyExistingEmoteStatsForServerSince(SERVER_ID, Instant.EPOCH, UsedEmoteType.REACTION.name())).thenReturn(results); + List returnedResult = testUnit.loadActiveEmoteStatsForServerSince(server, Instant.EPOCH, UsedEmoteType.REACTION); Assert.assertEquals(results.size(), returnedResult.size()); Assert.assertEquals(results, returnedResult); } diff --git a/abstracto-application/abstracto-modules/statistic/statistic-int/src/main/java/dev/sheldan/abstracto/statistic/config/StatisticSlashCommandNames.java b/abstracto-application/abstracto-modules/statistic/statistic-int/src/main/java/dev/sheldan/abstracto/statistic/config/StatisticSlashCommandNames.java new file mode 100644 index 000000000..0e24ab070 --- /dev/null +++ b/abstracto-application/abstracto-modules/statistic/statistic-int/src/main/java/dev/sheldan/abstracto/statistic/config/StatisticSlashCommandNames.java @@ -0,0 +1,6 @@ +package dev.sheldan.abstracto.statistic.config; + +public class StatisticSlashCommandNames { + public static final String STATISTIC = "statistics"; + public static final String STATISTIC_INTERNAL = "statisticsinteral"; +} diff --git a/abstracto-application/abstracto-modules/statistic/statistic-int/src/main/java/dev/sheldan/abstracto/statistic/emote/config/EmoteTrackingFeatureConfig.java b/abstracto-application/abstracto-modules/statistic/statistic-int/src/main/java/dev/sheldan/abstracto/statistic/emote/config/EmoteTrackingFeatureConfig.java index 6e12f9b2c..96cb17842 100644 --- a/abstracto-application/abstracto-modules/statistic/statistic-int/src/main/java/dev/sheldan/abstracto/statistic/emote/config/EmoteTrackingFeatureConfig.java +++ b/abstracto-application/abstracto-modules/statistic/statistic-int/src/main/java/dev/sheldan/abstracto/statistic/emote/config/EmoteTrackingFeatureConfig.java @@ -29,6 +29,9 @@ public class EmoteTrackingFeatureConfig implements FeatureConfig { */ @Override public List getAvailableModes() { - return Arrays.asList(EmoteTrackingMode.EXTERNAL_EMOTES, EmoteTrackingMode.AUTO_TRACK, EmoteTrackingMode.AUTO_TRACK_EXTERNAL); + return Arrays.asList(EmoteTrackingMode.EXTERNAL_EMOTES, + EmoteTrackingMode.AUTO_TRACK, + EmoteTrackingMode.AUTO_TRACK_EXTERNAL, + EmoteTrackingMode.TRACK_REACTIONS); } } diff --git a/abstracto-application/abstracto-modules/statistic/statistic-int/src/main/java/dev/sheldan/abstracto/statistic/emote/config/EmoteTrackingMode.java b/abstracto-application/abstracto-modules/statistic/statistic-int/src/main/java/dev/sheldan/abstracto/statistic/emote/config/EmoteTrackingMode.java index 661720d5b..7395ad9d8 100644 --- a/abstracto-application/abstracto-modules/statistic/statistic-int/src/main/java/dev/sheldan/abstracto/statistic/emote/config/EmoteTrackingMode.java +++ b/abstracto-application/abstracto-modules/statistic/statistic-int/src/main/java/dev/sheldan/abstracto/statistic/emote/config/EmoteTrackingMode.java @@ -9,10 +9,14 @@ import lombok.Getter; * Influences: * EXTERNAL_EMOTES: Enables the tracking of emotes which are not from the server the feature is enabled in. This feature alone only enables to track emotes with the `trackEmote` command and makes the command `externalEmoteStats` (and more) available * AUTO_TRACK_EXTERNAL: Every external emote which is encountered in a message will be tracked (created and updated), only works in combination with EXTERNAL_MOTES + * TRACK_REACTIONS: if reactions are counted */ @Getter public enum EmoteTrackingMode implements FeatureMode { - AUTO_TRACK("emoteAutoTrack"), EXTERNAL_EMOTES("externalEmotes"), AUTO_TRACK_EXTERNAL("autoTrackExternal"); + AUTO_TRACK("emoteAutoTrack"), + EXTERNAL_EMOTES("externalEmotes"), + AUTO_TRACK_EXTERNAL("autoTrackExternal"), + TRACK_REACTIONS("trackReactions"); private final String key; diff --git a/abstracto-application/abstracto-modules/statistic/statistic-int/src/main/java/dev/sheldan/abstracto/statistic/emote/model/PersistingEmote.java b/abstracto-application/abstracto-modules/statistic/statistic-int/src/main/java/dev/sheldan/abstracto/statistic/emote/model/PersistingEmote.java index f88d208bd..1585a333c 100644 --- a/abstracto-application/abstracto-modules/statistic/statistic-int/src/main/java/dev/sheldan/abstracto/statistic/emote/model/PersistingEmote.java +++ b/abstracto-application/abstracto-modules/statistic/statistic-int/src/main/java/dev/sheldan/abstracto/statistic/emote/model/PersistingEmote.java @@ -1,5 +1,6 @@ package dev.sheldan.abstracto.statistic.emote.model; +import dev.sheldan.abstracto.statistic.emote.model.database.UsedEmoteType; import lombok.Builder; import lombok.Getter; import lombok.Setter; @@ -33,6 +34,7 @@ public class PersistingEmote { * Only if the emote is external: the URL where the source image of the emote is stored on Discord servers */ private String externalUrl; + private UsedEmoteType usedEmoteType; /** * The amount of times the emote has been used. */ diff --git a/abstracto-application/abstracto-modules/statistic/statistic-int/src/main/java/dev/sheldan/abstracto/statistic/emote/model/database/UsedEmote.java b/abstracto-application/abstracto-modules/statistic/statistic-int/src/main/java/dev/sheldan/abstracto/statistic/emote/model/database/UsedEmote.java index e0f755663..71873bfa4 100644 --- a/abstracto-application/abstracto-modules/statistic/statistic-int/src/main/java/dev/sheldan/abstracto/statistic/emote/model/database/UsedEmote.java +++ b/abstracto-application/abstracto-modules/statistic/statistic-int/src/main/java/dev/sheldan/abstracto/statistic/emote/model/database/UsedEmote.java @@ -47,4 +47,5 @@ public class UsedEmote { @Column(name = "updated", insertable = false, updatable = false) private Instant updated; + } diff --git a/abstracto-application/abstracto-modules/statistic/statistic-int/src/main/java/dev/sheldan/abstracto/statistic/emote/model/database/UsedEmoteType.java b/abstracto-application/abstracto-modules/statistic/statistic-int/src/main/java/dev/sheldan/abstracto/statistic/emote/model/database/UsedEmoteType.java new file mode 100644 index 000000000..dd8fa342e --- /dev/null +++ b/abstracto-application/abstracto-modules/statistic/statistic-int/src/main/java/dev/sheldan/abstracto/statistic/emote/model/database/UsedEmoteType.java @@ -0,0 +1,6 @@ +package dev.sheldan.abstracto.statistic.emote.model.database; + +public enum UsedEmoteType { + MESSAGE, + REACTION; +} diff --git a/abstracto-application/abstracto-modules/statistic/statistic-int/src/main/java/dev/sheldan/abstracto/statistic/emote/model/database/embed/UsedEmoteDay.java b/abstracto-application/abstracto-modules/statistic/statistic-int/src/main/java/dev/sheldan/abstracto/statistic/emote/model/database/embed/UsedEmoteDay.java index 57a5292cd..a11b386c9 100644 --- a/abstracto-application/abstracto-modules/statistic/statistic-int/src/main/java/dev/sheldan/abstracto/statistic/emote/model/database/embed/UsedEmoteDay.java +++ b/abstracto-application/abstracto-modules/statistic/statistic-int/src/main/java/dev/sheldan/abstracto/statistic/emote/model/database/embed/UsedEmoteDay.java @@ -1,5 +1,8 @@ package dev.sheldan.abstracto.statistic.emote.model.database.embed; +import dev.sheldan.abstracto.statistic.emote.model.database.UsedEmoteType; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; import lombok.*; import jakarta.persistence.Column; @@ -36,4 +39,8 @@ public class UsedEmoteDay implements Serializable { */ @Column(name = "use_date") private Instant useDate; + + @Enumerated(EnumType.STRING) + @Column(name = "type") + private UsedEmoteType type; } diff --git a/abstracto-application/abstracto-modules/statistic/statistic-int/src/main/java/dev/sheldan/abstracto/statistic/emote/service/TrackedEmoteRuntimeService.java b/abstracto-application/abstracto-modules/statistic/statistic-int/src/main/java/dev/sheldan/abstracto/statistic/emote/service/TrackedEmoteRuntimeService.java index d3893be90..b1324895f 100644 --- a/abstracto-application/abstracto-modules/statistic/statistic-int/src/main/java/dev/sheldan/abstracto/statistic/emote/service/TrackedEmoteRuntimeService.java +++ b/abstracto-application/abstracto-modules/statistic/statistic-int/src/main/java/dev/sheldan/abstracto/statistic/emote/service/TrackedEmoteRuntimeService.java @@ -2,6 +2,7 @@ package dev.sheldan.abstracto.statistic.emote.service; import dev.sheldan.abstracto.core.models.cache.CachedEmote; import dev.sheldan.abstracto.statistic.emote.model.PersistingEmote; +import dev.sheldan.abstracto.statistic.emote.model.database.UsedEmoteType; import net.dv8tion.jda.api.entities.Guild; import java.util.List; @@ -18,24 +19,16 @@ public interface TrackedEmoteRuntimeService { */ Map>> getRuntimeConfig(); - /** - * Adds the given {@link net.dv8tion.jda.api.entities.emoji.CustomEmoji} used in the {@link Guild} to the runtime storage. - * The necessary lock will be acquired by this method. - * @param emote The {@link CachedEmote} to add to the runtime storage - * @param guild The {@link Guild} in which the {@link net.dv8tion.jda.api.entities.emoji.CustomEmoji} is used - * @param external Whether or not the emote is external - */ - void addEmoteForServer(CachedEmote emote, Guild guild, boolean external); - /** * Adds the given {@link CachedEmote} used in the {@link Guild} to the runtime storage. * The necessary lock will be acquired by this method. * @param emote The {@link CachedEmote} to add to the runtime storage * @param guild The {@link Guild} in which the {@link net.dv8tion.jda.api.entities.emoji.CustomEmoji} is used * @param count The amount of usages which should be added - * @param external Whether or not the emote is external + * @param external Whether the emote is external + * @param usedEmoteType The type of the emote */ - void addEmoteForServer(CachedEmote emote, Guild guild, Long count, boolean external); + void addEmoteForServer(CachedEmote emote, Guild guild, Long count, boolean external, UsedEmoteType usedEmoteType); /** * Calculates the key used for the Map containing the emote statistics. @@ -50,7 +43,7 @@ public interface TrackedEmoteRuntimeService { * @param external Whether or not the {@link net.dv8tion.jda.api.entities.emoji.CustomEmoji} is external * @return A created {@link PersistingEmote} instance from the {@link net.dv8tion.jda.api.entities.emoji.CustomEmoji} */ - PersistingEmote createFromEmote(Guild guild, CachedEmote emote, boolean external); + PersistingEmote createFromEmote(Guild guild, CachedEmote emote, boolean external, UsedEmoteType type); /** * Creates a {@link PersistingEmote} from the given parameters. @@ -60,7 +53,7 @@ public interface TrackedEmoteRuntimeService { * @param external Whether or not the {@link net.dv8tion.jda.api.entities.emoji.CustomEmoji} is external * @return A created {@link PersistingEmote} instance from the {@link net.dv8tion.jda.api.entities.emoji.CustomEmoji} */ - PersistingEmote createFromEmote(Guild guild, CachedEmote emote, Long count, boolean external); + PersistingEmote createFromEmote(Guild guild, CachedEmote emote, Long count, boolean external, UsedEmoteType usedEmoteType); /** * Acquires the lock which should be used when accessing the runtime storage diff --git a/abstracto-application/abstracto-modules/statistic/statistic-int/src/main/java/dev/sheldan/abstracto/statistic/emote/service/TrackedEmoteService.java b/abstracto-application/abstracto-modules/statistic/statistic-int/src/main/java/dev/sheldan/abstracto/statistic/emote/service/TrackedEmoteService.java index 5e16ac999..eb075ae97 100644 --- a/abstracto-application/abstracto-modules/statistic/statistic-int/src/main/java/dev/sheldan/abstracto/statistic/emote/service/TrackedEmoteService.java +++ b/abstracto-application/abstracto-modules/statistic/statistic-int/src/main/java/dev/sheldan/abstracto/statistic/emote/service/TrackedEmoteService.java @@ -5,6 +5,7 @@ import dev.sheldan.abstracto.statistic.emote.model.PersistingEmote; import dev.sheldan.abstracto.statistic.emote.model.TrackedEmoteOverview; import dev.sheldan.abstracto.statistic.emote.model.TrackedEmoteSynchronizationResult; import dev.sheldan.abstracto.statistic.emote.model.database.TrackedEmote; +import dev.sheldan.abstracto.statistic.emote.model.database.UsedEmoteType; import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.emoji.CustomEmoji; @@ -21,23 +22,25 @@ public interface TrackedEmoteService { * @param emotes The list of {@link CustomEmoji}s to add to the runtime storage * @param guild The {@link Guild} in which the {@link CustomEmoji}s were used and where the usages should be added */ - void addEmoteToRuntimeStorage(List emotes, Guild guild); + void addEmoteToRuntimeStorage(List emotes, Guild guild, UsedEmoteType usedEmoteType); /** * Adds the given {@link CustomEmoji} with the given amount to the runtime storage for the given {@link Guild} * @param emote The {@link CachedEmote} to add to the runtime storage * @param guild The {@link Guild} in which the {@link CustomEmoji} was used and in which the usage should be added * @param count The amount of times which the {@link CustomEmoji} has been used and should be reflected in the runtime storage + * @param type The type of interaction the emote came from */ - void addEmoteToRuntimeStorage(CachedEmote emote, Guild guild, Long count); + void addEmoteToRuntimeStorage(CachedEmote emote, Guild guild, Long count, UsedEmoteType type); /** * Adds the given {@link CustomEmoji} with the given amount to the runtime storage for the given {@link Guild} * @param emote The {@link CustomEmoji} to add to the runtime storage * @param guild The {@link Guild} in which the {@link CustomEmoji} was used and in which the usage should be added * @param count The amount of times which the {@link CustomEmoji} has been used and should be reflected in the runtime storage + * @param type The type of interaction the emote came from */ - void addEmoteToRuntimeStorage(CustomEmoji emote, Guild guild, Long count); + void addEmoteToRuntimeStorage(CustomEmoji emote, Guild guild, Long count, UsedEmoteType type); /** * Takes the given map of server_ids with the list of {@link PersistingEmote} and stores the objects in the database diff --git a/abstracto-application/abstracto-modules/statistic/statistic-int/src/main/java/dev/sheldan/abstracto/statistic/emote/service/UsedEmoteService.java b/abstracto-application/abstracto-modules/statistic/statistic-int/src/main/java/dev/sheldan/abstracto/statistic/emote/service/UsedEmoteService.java index ece32428d..0138cddbf 100644 --- a/abstracto-application/abstracto-modules/statistic/statistic-int/src/main/java/dev/sheldan/abstracto/statistic/emote/service/UsedEmoteService.java +++ b/abstracto-application/abstracto-modules/statistic/statistic-int/src/main/java/dev/sheldan/abstracto/statistic/emote/service/UsedEmoteService.java @@ -5,6 +5,7 @@ import dev.sheldan.abstracto.statistic.emote.model.EmoteStatsModel; import dev.sheldan.abstracto.statistic.emote.model.EmoteStatsResultDisplay; import dev.sheldan.abstracto.statistic.emote.model.database.TrackedEmote; +import dev.sheldan.abstracto.statistic.emote.model.database.UsedEmoteType; import java.time.Instant; /** @@ -25,29 +26,32 @@ public interface UsedEmoteService { * This {@link EmoteStatsModel} will contain only deleted {@link TrackedEmote} from the server * @param server The {@link AServer} to retrieve the emote stats for * @param since Emote stats should be younger than this {@link Instant}. Only the date portion is considered. + * @param usedEmoteType The type of interaction the emote was from * @return An {@link EmoteStatsModel} containing the statistics split by animated and static emote */ - EmoteStatsModel getDeletedEmoteStatsForServerSince(AServer server, Instant since); + EmoteStatsModel getDeletedEmoteStatsForServerSince(AServer server, Instant since, UsedEmoteType usedEmoteType); /** * Retrieves the {@link EmoteStatsModel} for the {@link AServer} since {@link Instant}. * This {@link EmoteStatsModel} will contain only external {@link TrackedEmote} from the server * @param server The {@link AServer} to retrieve the emote stats for * @param since Emote stats should be younger than this {@link Instant}. Only the date portion is considered. + * @param usedEmoteType The type of interaction the emote was used in * @return An {@link EmoteStatsModel} containing the statistics split by animated and static emote */ - EmoteStatsModel getExternalEmoteStatsForServerSince(AServer server, Instant since); + EmoteStatsModel getExternalEmoteStatsForServerSince(AServer server, Instant since, UsedEmoteType usedEmoteType); /** * Retrieves the {@link EmoteStatsModel} for the {@link AServer} since {@link Instant}. * This {@link EmoteStatsModel} will contain only active {@link TrackedEmote} from the server. These are emotes which are still present * the {@link net.dv8tion.jda.api.entities.Guild} * @param server The {@link AServer} to retrieve the emote stats for - * @param since Emote stats should be younger than this {@link Instant}. Only the date portion is considered. + * @param since Emote stats should be younger than this {@link Instant}. Only the date portion is considered + * @param usedEmoteType The type of emote the interaction is coming from * @return An {@link EmoteStatsModel} containing the statistics split by animated and static emote */ - EmoteStatsModel getActiveEmoteStatsForServerSince(AServer server, Instant since); - EmoteStatsResultDisplay getEmoteStatForEmote(TrackedEmote trackedEmote, Instant since); + EmoteStatsModel getActiveEmoteStatsForServerSince(AServer server, Instant since, UsedEmoteType usedEmoteType); + EmoteStatsResultDisplay getEmoteStatForEmote(TrackedEmote trackedEmote, Instant since, UsedEmoteType usedEmoteType); /** * Removes all {@link dev.sheldan.abstracto.statistic.emote.model.database.UsedEmote} for the given {@link TrackedEmote} which are younger diff --git a/abstracto-application/abstracto-modules/statistic/statistic-int/src/main/java/dev/sheldan/abstracto/statistic/emote/service/management/UsedEmoteManagementService.java b/abstracto-application/abstracto-modules/statistic/statistic-int/src/main/java/dev/sheldan/abstracto/statistic/emote/service/management/UsedEmoteManagementService.java index 2dc406aee..6c2e65b3f 100644 --- a/abstracto-application/abstracto-modules/statistic/statistic-int/src/main/java/dev/sheldan/abstracto/statistic/emote/service/management/UsedEmoteManagementService.java +++ b/abstracto-application/abstracto-modules/statistic/statistic-int/src/main/java/dev/sheldan/abstracto/statistic/emote/service/management/UsedEmoteManagementService.java @@ -5,6 +5,7 @@ import dev.sheldan.abstracto.statistic.emote.model.EmoteStatsResult; import dev.sheldan.abstracto.statistic.emote.model.database.TrackedEmote; import dev.sheldan.abstracto.statistic.emote.model.database.UsedEmote; +import dev.sheldan.abstracto.statistic.emote.model.database.UsedEmoteType; import java.time.Instant; import java.util.List; import java.util.Optional; @@ -17,26 +18,28 @@ public interface UsedEmoteManagementService { * Loads an {@link Optional} containing a {@link UsedEmote} for the particular {@link TrackedEmote} for the current day. * The {@link Optional} is empty, if none exists. * @param trackedEmote The {@link TrackedEmote} to search a {@link UsedEmote} for today + * @param usedEmoteType The type of interaction this emote was used in * @return An {@link Optional} containing a {@link UsedEmote}, if it exists for the current day */ - Optional loadUsedEmoteForTrackedEmoteToday(TrackedEmote trackedEmote); + Optional loadUsedEmoteForTrackedEmoteToday(TrackedEmote trackedEmote, UsedEmoteType usedEmoteType); /** * Creates and persists and instance of {@link UsedEmote} from the given {@link TrackedEmote}, with the defined count and the current date. * @param trackedEmote The {@link TrackedEmote} for which to create a {@link UsedEmote} for * @param count The amount of usages for the {@link UsedEmote} - * @return The created {@link UsedEmote} instance int he database + * @param type The type of emote + * @return The created {@link UsedEmote} instance in the database */ - UsedEmote createEmoteUsageForToday(TrackedEmote trackedEmote, Long count); + UsedEmote createEmoteUsageForToday(TrackedEmote trackedEmote, Long count, UsedEmoteType type); /** * Creates and persists and instance of {@link UsedEmote} from the given {@link TrackedEmote}, with the defined count and the given date. * @param trackedEmote The {@link TrackedEmote} for which to create a {@link UsedEmote} for * @param count The amount of usages for the {@link UsedEmote} * @param instant The date to create the {@link UsedEmote emoteUsage} for - * @return The created {@link UsedEmote} instance int he database + * @return The created {@link UsedEmote} instance in the database */ - UsedEmote createEmoteUsageFor(TrackedEmote trackedEmote, Long count, Instant instant); + UsedEmote createEmoteUsageFor(TrackedEmote trackedEmote, Long count, Instant instant, UsedEmoteType type); /** * Loads {@link UsedEmote} for the {@link AServer} which are newer than the given {@link Instant} @@ -58,26 +61,29 @@ public interface UsedEmoteManagementService { * Load {@link EmoteStatsResult} from the {@link AServer} for {@link TrackedEmote} which were deleted and newer than {@link Instant} * @param server The {@link AServer} to retrieve the emote statistics for * @param since Emote stats should be younger than this {@link Instant}. Only the date portion is considered. + * @param usedEmoteType The type of interaction the emote was used in * @return A list of {@link EmoteStatsResult} from the {@link AServer} newer than the given {@link Instant} for all deleted {@link TrackedEmote} */ - List loadDeletedEmoteStatsForServerSince(AServer server, Instant since); + List loadDeletedEmoteStatsForServerSince(AServer server, Instant since, UsedEmoteType usedEmoteType); /** * Load {@link EmoteStatsResult} from the {@link AServer} for {@link TrackedEmote} which are external and newer than {@link Instant} * @param server The {@link AServer} to retrieve the emote statistic for * @param since Emote stats should be younger than this {@link Instant}. Only the date portion is considered. + * @param type The type of interaction the emote was used in * @return A list of {@link EmoteStatsResult} from the {@link AServer} newer than the given {@link Instant} for all external {@link TrackedEmote} */ - List loadExternalEmoteStatsForServerSince(AServer server, Instant since); + List loadExternalEmoteStatsForServerSince(AServer server, Instant since, UsedEmoteType type); /** * Load {@link EmoteStatsResult} from the {@link AServer} for {@link TrackedEmote} which are active and newer than {@link Instant} * @param server The {@link AServer} to retrieve the emote statistic for * @param since Emote stats should be younger than this {@link Instant}. Only the date portion is considered. + * @param type The type of emote that should be loaded * @return A list of {@link EmoteStatsResult} from the {@link AServer} newer than the given {@link Instant} for all active {@link TrackedEmote} */ - List loadActiveEmoteStatsForServerSince(AServer server, Instant since); - EmoteStatsResult loadEmoteStatForEmote(TrackedEmote trackedEmote, Instant since); + List loadActiveEmoteStatsForServerSince(AServer server, Instant since, UsedEmoteType type); + EmoteStatsResult loadEmoteStatForEmote(TrackedEmote trackedEmote, Instant since, UsedEmoteType usedEmoteType); /** * Deletes all emote usages for the {@link TrackedEmote} which are younger than the given {@link Instant} diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/commands/utility/SetEmote.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/commands/utility/SetEmote.java index 210b10fae..d4d7fe466 100644 --- a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/commands/utility/SetEmote.java +++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/commands/utility/SetEmote.java @@ -51,7 +51,7 @@ public class SetEmote extends AbstractConditionableCommand { public CompletableFuture executeSlash(SlashCommandInteractionEvent event) { String emoteKey = slashCommandParameterService.getCommandOption(EMOTE_KEY_PARAMETER, event, String.class); String emote = slashCommandParameterService.getCommandOption(EMOTE_PARAMETER, event, String.class); - AEmote aEmote = slashCommandParameterService.loadAEmoteFromString(emote, event); + AEmote aEmote = slashCommandParameterService.loadAEmoteFromString(emote, event.getGuild()); emoteManagementService.setEmoteToAEmote(emoteKey, aEmote, event.getGuild().getIdLong()); return interactionService.replyEmbed(RESPONSE_TEMPLATE, new Object(), event) .thenApply(interactionHook -> CommandResult.fromSuccess()); diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/interaction/InteractionServiceBean.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/interaction/InteractionServiceBean.java index 5e080cb30..575df2c34 100644 --- a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/interaction/InteractionServiceBean.java +++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/interaction/InteractionServiceBean.java @@ -10,7 +10,6 @@ import dev.sheldan.abstracto.core.service.management.ServerManagementService; import dev.sheldan.abstracto.core.templating.model.AttachedFile; import dev.sheldan.abstracto.core.templating.model.MessageToSend; import dev.sheldan.abstracto.core.templating.service.TemplateService; -import dev.sheldan.abstracto.core.utils.FileService; import lombok.extern.slf4j.Slf4j; import net.dv8tion.jda.api.entities.Message; import net.dv8tion.jda.api.entities.MessageEmbed; @@ -167,7 +166,7 @@ public class InteractionServiceBean implements InteractionService { } @Override - public CompletableFuture editOriginal(MessageToSend messageToSend, InteractionHook interactionHook) { + public CompletableFuture replaceOriginal(MessageToSend messageToSend, InteractionHook interactionHook) { Long serverId = interactionHook.getInteraction().getGuild().getIdLong(); if(messageToSend.getEphemeral()) { @@ -231,9 +230,17 @@ public class InteractionServiceBean implements InteractionService { if(action == null) { throw new AbstractoRunTimeException("The callback did not result in any message."); } + action.setReplace(true); return action.submit(); } + @Override + public CompletableFuture replaceOriginal(String template, Object model, InteractionHook interactionHook) { + Long serverId = interactionHook.getInteraction().getGuild().getIdLong(); + MessageToSend messageToSend = templateService.renderEmbedTemplate(template, new Object(), serverId); + return replaceOriginal(messageToSend, interactionHook); + } + public CompletableFuture replyMessageToSend(MessageToSend messageToSend, IReplyCallback callback) { Long serverId = callback.getGuild().getIdLong(); ReplyCallbackAction action = null; diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/interaction/job/ConfirmationCleanupJob.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/interaction/job/ConfirmationCleanupJob.java index e8cbdd4e1..ef92af454 100644 --- a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/interaction/job/ConfirmationCleanupJob.java +++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/interaction/job/ConfirmationCleanupJob.java @@ -1,6 +1,7 @@ package dev.sheldan.abstracto.core.interaction.job; import dev.sheldan.abstracto.core.command.CommandReceivedHandler; +import dev.sheldan.abstracto.core.interaction.slash.SlashCommandListenerBean; import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.quartz.DisallowConcurrentExecution; @@ -23,18 +24,28 @@ public class ConfirmationCleanupJob extends QuartzJobBean { private Long messageId; private String confirmationPayloadId; private String abortPayloadId; + private Long interactionId; @Autowired private CommandReceivedHandler commandReceivedHandler; + @Autowired + private SlashCommandListenerBean slashCommandListenerBean; + @Override protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException { - log.info("Cleaning up confirmation message {} in server {} in channel {}.", messageId, serverId, channelId); - commandReceivedHandler.cleanupConfirmationMessage(serverId, channelId, messageId, confirmationPayloadId, abortPayloadId) - .thenAccept(unused -> log.info("Deleted confirmation message {}", messageId)) - .exceptionally(throwable -> { - log.warn("Failed to cleanup confirmation message {}.", messageId); - return null; - }); + // we either clean up a slash command confirmation or a message command interaction + if(interactionId == null) { + log.info("Cleaning up confirmation message {} in server {} in channel {}.", messageId, serverId, channelId); + commandReceivedHandler.cleanupConfirmationMessage(serverId, channelId, messageId, confirmationPayloadId, abortPayloadId) + .thenAccept(unused -> log.info("Deleted confirmation message {}", messageId)) + .exceptionally(throwable -> { + log.warn("Failed to cleanup confirmation message {}.", messageId); + return null; + }); + } else { + log.info("Cleaning up slash command confirmation message in server {}.", serverId); + slashCommandListenerBean.removeSlashCommandConfirmationInteraction(interactionId, confirmationPayloadId, abortPayloadId); + } } } diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/interaction/slash/DriedSlashCommand.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/interaction/slash/DriedSlashCommand.java new file mode 100644 index 000000000..6b2347af3 --- /dev/null +++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/interaction/slash/DriedSlashCommand.java @@ -0,0 +1,13 @@ +package dev.sheldan.abstracto.core.interaction.slash; + +import dev.sheldan.abstracto.core.command.Command; +import lombok.Builder; +import lombok.Getter; +import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; + +@Getter +@Builder +public class DriedSlashCommand { + private Command command; + private SlashCommandInteractionEvent event; +} diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/interaction/slash/SlashCommandListenerBean.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/interaction/slash/SlashCommandListenerBean.java index 731c78566..4b80badc1 100644 --- a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/interaction/slash/SlashCommandListenerBean.java +++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/interaction/slash/SlashCommandListenerBean.java @@ -1,19 +1,41 @@ package dev.sheldan.abstracto.core.interaction.slash; +import static dev.sheldan.abstracto.core.command.CommandReceivedHandler.COMMAND_CONFIRMATION_MESSAGE_TEMPLATE_KEY; + import dev.sheldan.abstracto.core.command.Command; import dev.sheldan.abstracto.core.command.CommandReceivedHandler; import dev.sheldan.abstracto.core.command.condition.ConditionResult; +import dev.sheldan.abstracto.core.command.config.features.CoreFeatureConfig; import dev.sheldan.abstracto.core.command.execution.CommandResult; import dev.sheldan.abstracto.core.command.service.CommandService; import dev.sheldan.abstracto.core.command.service.PostCommandExecution; import dev.sheldan.abstracto.core.exception.AbstractoRunTimeException; +import dev.sheldan.abstracto.core.interaction.ComponentPayloadManagementService; +import dev.sheldan.abstracto.core.interaction.ComponentPayloadService; +import dev.sheldan.abstracto.core.interaction.ComponentService; +import dev.sheldan.abstracto.core.interaction.InteractionService; +import dev.sheldan.abstracto.core.interaction.button.CommandConfirmationModel; +import dev.sheldan.abstracto.core.interaction.slash.payload.SlashCommandConfirmationPayload; import dev.sheldan.abstracto.core.metric.service.CounterMetric; import dev.sheldan.abstracto.core.metric.service.MetricService; import dev.sheldan.abstracto.core.metric.service.MetricTag; +import dev.sheldan.abstracto.core.models.database.AServer; +import dev.sheldan.abstracto.core.service.ConfigService; +import dev.sheldan.abstracto.core.service.management.ServerManagementService; +import dev.sheldan.abstracto.scheduling.model.JobParameters; +import dev.sheldan.abstracto.scheduling.service.SchedulerService; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import lombok.Getter; import lombok.extern.slf4j.Slf4j; +import net.dv8tion.jda.api.entities.Message; import net.dv8tion.jda.api.events.interaction.command.CommandAutoCompleteInteractionEvent; import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; +import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent; import net.dv8tion.jda.api.hooks.ListenerAdapter; import net.dv8tion.jda.api.interactions.commands.build.OptionData; import org.springframework.beans.factory.annotation.Autowired; @@ -60,6 +82,30 @@ public class SlashCommandListenerBean extends ListenerAdapter { @Autowired private MetricService metricService; + @Autowired + private ComponentService componentService; + + @Autowired + private ComponentPayloadService componentPayloadService; + + @Autowired + private ServerManagementService serverManagementService; + + @Autowired + private InteractionService interactionService; + + @Autowired + private ConfigService configService; + + @Autowired + private SchedulerService schedulerService; + + @Autowired + private ComponentPayloadManagementService componentPayloadManagementService; + + private static final Map COMMANDS_WAITING_FOR_CONFIRMATION = new ConcurrentHashMap<>(); + public static final String SLASH_COMMAND_CONFIRMATION_ORIGIN = "SLASH_COMMAND_CONFIRMATION"; + public static final CounterMetric SLASH_COMMANDS_PROCESSED_COUNTER = CounterMetric .builder() .name(CommandReceivedHandler.COMMAND_PROCESSED) @@ -140,25 +186,118 @@ public class SlashCommandListenerBean extends ListenerAdapter { }); } - @Transactional(rollbackFor = AbstractoRunTimeException.class) - public void executeCommand(SlashCommandInteractionEvent event, Command command, ConditionResult conditionResult) { - CompletableFuture commandOutput; - if(conditionResult.isResult()) { - commandOutput = command.executeSlash(event).thenApply(commandResult -> { - log.info("Command {} in server {} was executed.", command.getConfiguration().getName(), event.getGuild().getIdLong()); + @Transactional + public void continueSlashCommand(Long interactionId, ButtonInteractionEvent buttonInteractionEvent) { + if(COMMANDS_WAITING_FOR_CONFIRMATION.containsKey(interactionId)) { + DriedSlashCommand driedSlashCommand = COMMANDS_WAITING_FOR_CONFIRMATION.get(interactionId); + Command commandInstance = driedSlashCommand.getCommand(); + String commandName = commandInstance.getConfiguration().getName(); + log.info("Continuing slash command {}", commandName); + commandInstance.executeSlash(driedSlashCommand.getEvent()).thenApply(commandResult -> { + log.info("Command {} in server {} was executed after confirmation.", commandName, buttonInteractionEvent.getGuild().getIdLong()); return commandResult; + }).thenAccept(commandResult -> { + self.executePostCommandListener(commandInstance, driedSlashCommand.getEvent(), commandResult); + COMMANDS_WAITING_FOR_CONFIRMATION.remove(interactionId); + }).exceptionally(throwable -> { + log.error("Error while handling post execution of command with confirmation {}", commandName, throwable); + CommandResult commandResult = CommandResult.fromError(throwable.getMessage(), throwable); + self.executePostCommandListener(commandInstance, driedSlashCommand.getEvent(), commandResult); + COMMANDS_WAITING_FOR_CONFIRMATION.remove(interactionId); + return null; }); } else { - commandOutput = CompletableFuture.completedFuture(CommandResult.fromCondition(conditionResult)); + log.warn("Interaction was not found in internal map - not continuing interaction from user {} in server {}.", buttonInteractionEvent.getUser().getIdLong(), buttonInteractionEvent.getGuild().getIdLong()); } - commandOutput.thenAccept(commandResult -> { - self.executePostCommandListener(command, event, commandResult); - }).exceptionally(throwable -> { - log.error("Error while handling post execution of command {}", command.getConfiguration().getName(), throwable); - CommandResult commandResult = CommandResult.fromError(throwable.getMessage(), throwable); - self.executePostCommandListener(command, event, commandResult); - return null; - }); + } + + @Transactional + public void removeSlashCommandConfirmationInteraction(Long interactionId, String confirmationPayload, String abortPayload) { + if(COMMANDS_WAITING_FOR_CONFIRMATION.containsKey(interactionId)) { + DriedSlashCommand removedSlashCommand = COMMANDS_WAITING_FOR_CONFIRMATION.remove(interactionId); + SlashCommandInteractionEvent event = removedSlashCommand.getEvent(); + event.getInteraction().getHook().deleteOriginal().queue(); + log.info("Remove interaction for command {} in server {} from user {}.", removedSlashCommand.getCommand().getConfiguration().getName(), event.getGuild().getIdLong(), event.getUser().getIdLong()); + } else { + log.info("Did not find interaction to clean up."); + } + componentPayloadManagementService.deletePayloads(Arrays.asList(confirmationPayload, abortPayload)); + } + + @Transactional(rollbackFor = AbstractoRunTimeException.class) + public void executeCommand(SlashCommandInteractionEvent event, Command command, ConditionResult conditionResult) { + String commandName = command.getConfiguration().getName(); + if(command.getConfiguration().isRequiresConfirmation() && conditionResult.isResult()) { + DriedSlashCommand slashCommand = DriedSlashCommand + .builder() + .command(command) + .event(event) + .build(); + COMMANDS_WAITING_FOR_CONFIRMATION.put(event.getIdLong(), slashCommand); + String confirmationId = componentService.generateComponentId(); + String abortId = componentService.generateComponentId(); + SlashCommandConfirmationPayload confirmPayload = SlashCommandConfirmationPayload + .builder() + .action(SlashCommandConfirmationPayload.CommandConfirmationAction.CONFIRM) + .interactionId(event.getIdLong()) + .build(); + Long serverId = event.getGuild().getIdLong(); + AServer server = serverManagementService.loadServer(event.getGuild()); + componentPayloadService.createButtonPayload(confirmationId, confirmPayload, SLASH_COMMAND_CONFIRMATION_ORIGIN, server); + SlashCommandConfirmationPayload denialPayload = SlashCommandConfirmationPayload + .builder() + .action(SlashCommandConfirmationPayload.CommandConfirmationAction.ABORT) + .interactionId(event.getIdLong()) + .build(); + componentPayloadService.createButtonPayload(abortId, denialPayload, SLASH_COMMAND_CONFIRMATION_ORIGIN, server); + CommandConfirmationModel model = CommandConfirmationModel + .builder() + .abortButtonId(abortId) + .confirmButtonId(confirmationId) + .commandName(commandName) + .build(); + Long userId = event.getUser().getIdLong(); + interactionService.replyEmbed(COMMAND_CONFIRMATION_MESSAGE_TEMPLATE_KEY, model, event).thenAccept(interactionHook -> { + log.info("Sent confirmation for command {} in server {} for user {}.", commandName, serverId, userId); + }).exceptionally(throwable -> { + log.warn("Failed to send confirmation for command {} in server {} for user {}.", commandName, serverId, userId); + return null; + }); + scheduleConfirmationDeletion(event.getIdLong(), confirmationId, abortId, serverId); + } else { + CompletableFuture commandOutput; + if(conditionResult.isResult()) { + commandOutput = command.executeSlash(event).thenApply(commandResult -> { + log.info("Command {} in server {} was executed.", commandName, event.getGuild().getIdLong()); + return commandResult; + }); + } else { + commandOutput = CompletableFuture.completedFuture(CommandResult.fromCondition(conditionResult)); + } + commandOutput.thenAccept(commandResult -> { + self.executePostCommandListener(command, event, commandResult); + }).exceptionally(throwable -> { + log.error("Error while handling post execution of command {}", commandName, throwable); + CommandResult commandResult = CommandResult.fromError(throwable.getMessage(), throwable); + self.executePostCommandListener(command, event, commandResult); + return null; + }); + } + } + + private void scheduleConfirmationDeletion(Long interactionId, String confirmationPayloadId, String abortPayloadId, Long serverId) { + HashMap parameters = new HashMap<>(); + parameters.put("interactionId", interactionId.toString()); + parameters.put("confirmationPayloadId", confirmationPayloadId); + parameters.put("abortPayloadId", abortPayloadId); + JobParameters jobParameters = JobParameters + .builder() + .parameters(parameters) + .build(); + Long confirmationTimeout = configService.getLongValueOrConfigDefault(CoreFeatureConfig.CONFIRMATION_TIMEOUT, serverId); + Instant targetDate = Instant.now().plus(confirmationTimeout, ChronoUnit.SECONDS); + log.info("Scheduling job to delete slash command confirmation in server {} at {}.", serverId, targetDate); + schedulerService.executeJobWithParametersOnce("confirmationCleanupJob", "core", jobParameters, Date.from(targetDate)); } @Transactional(propagation = Propagation.REQUIRES_NEW) diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/interaction/slash/SlashCommandServiceBean.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/interaction/slash/SlashCommandServiceBean.java index 861423704..f31133e29 100644 --- a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/interaction/slash/SlashCommandServiceBean.java +++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/interaction/slash/SlashCommandServiceBean.java @@ -2,17 +2,20 @@ package dev.sheldan.abstracto.core.interaction.slash; import dev.sheldan.abstracto.core.command.config.CommandConfiguration; import dev.sheldan.abstracto.core.command.config.Parameter; +import dev.sheldan.abstracto.core.command.execution.CommandResult; import dev.sheldan.abstracto.core.command.model.database.ACommand; import dev.sheldan.abstracto.core.command.model.database.ACommandInAServer; import dev.sheldan.abstracto.core.command.service.management.CommandInServerManagementService; import dev.sheldan.abstracto.core.command.service.management.CommandManagementService; import dev.sheldan.abstracto.core.config.FeatureDefinition; +import dev.sheldan.abstracto.core.interaction.InteractionService; import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParameterService; import dev.sheldan.abstracto.core.service.FeatureConfigService; import dev.sheldan.abstracto.core.service.FeatureFlagService; import dev.sheldan.abstracto.core.templating.service.TemplateService; import dev.sheldan.abstracto.core.utils.CompletableFutureList; import net.dv8tion.jda.api.entities.Guild; +import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import net.dv8tion.jda.api.interactions.commands.Command; import net.dv8tion.jda.api.interactions.commands.OptionType; import net.dv8tion.jda.api.interactions.commands.build.*; @@ -52,6 +55,9 @@ public class SlashCommandServiceBean implements SlashCommandService { @Autowired private FeatureFlagService featureFlagService; + @Autowired + private InteractionService interactionService; + @Override public void convertCommandConfigToCommandData(CommandConfiguration commandConfiguration, List, SlashCommandData>> existingCommands, Long serverId) { boolean isTemplated = commandConfiguration.isTemplated(); @@ -107,6 +113,22 @@ public class SlashCommandServiceBean implements SlashCommandService { } } + @Override + public CompletableFuture completeConfirmableCommand(SlashCommandInteractionEvent event, String template) { + return completeConfirmableCommand(event, template, new Object()); + } + + @Override + public CompletableFuture completeConfirmableCommand(SlashCommandInteractionEvent event, String template, Object parameter) { + if(event.isAcknowledged()) { + return interactionService.replaceOriginal(template, parameter, event.getInteraction().getHook()) + .thenApply(interactionHook -> CommandResult.fromIgnored()); + } else { + return interactionService.replyMessage(template, parameter, event) + .thenApply(interactionHook -> CommandResult.fromIgnored()); + } + } + @Override public void convertCommandConfigToCommandData(CommandConfiguration commandConfiguration, List, SlashCommandData>> existingCommands) { convertCommandConfigToCommandData(commandConfiguration, existingCommands, null); diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/interaction/slash/listener/SlashCommandConfirmationGivenButtonListener.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/interaction/slash/listener/SlashCommandConfirmationGivenButtonListener.java new file mode 100644 index 000000000..fba7db115 --- /dev/null +++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/interaction/slash/listener/SlashCommandConfirmationGivenButtonListener.java @@ -0,0 +1,50 @@ +package dev.sheldan.abstracto.core.interaction.slash.listener; + +import static dev.sheldan.abstracto.core.interaction.slash.SlashCommandListenerBean.SLASH_COMMAND_CONFIRMATION_ORIGIN; + +import dev.sheldan.abstracto.core.command.config.features.CoreFeatureDefinition; +import dev.sheldan.abstracto.core.config.FeatureDefinition; +import dev.sheldan.abstracto.core.config.ListenerPriority; +import dev.sheldan.abstracto.core.interaction.button.listener.ButtonClickedListener; +import dev.sheldan.abstracto.core.interaction.button.listener.ButtonClickedListenerModel; +import dev.sheldan.abstracto.core.interaction.button.listener.ButtonClickedListenerResult; +import dev.sheldan.abstracto.core.interaction.slash.SlashCommandListenerBean; +import dev.sheldan.abstracto.core.interaction.slash.payload.SlashCommandConfirmationPayload; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +@Slf4j +public class SlashCommandConfirmationGivenButtonListener implements ButtonClickedListener { + + @Autowired + private SlashCommandListenerBean slashCommandListenerBean; + + + @Override + public ButtonClickedListenerResult execute(ButtonClickedListenerModel model) { + SlashCommandConfirmationPayload payload = (SlashCommandConfirmationPayload) model.getDeserializedPayload(); + if(payload.getAction().equals(SlashCommandConfirmationPayload.CommandConfirmationAction.CONFIRM)) { + slashCommandListenerBean.continueSlashCommand(payload.getInteractionId(), model.getEvent()); + return ButtonClickedListenerResult.ACKNOWLEDGED; + } else { + return ButtonClickedListenerResult.IGNORED; + } + } + + @Override + public Boolean handlesEvent(ButtonClickedListenerModel model) { + return model.getOrigin().equals(SLASH_COMMAND_CONFIRMATION_ORIGIN); + } + + @Override + public FeatureDefinition getFeature() { + return CoreFeatureDefinition.CORE_FEATURE; + } + + @Override + public Integer getPriority() { + return ListenerPriority.HIGHEST; + } +} diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/interaction/slash/parameter/SlashCommandParameterServiceBean.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/interaction/slash/parameter/SlashCommandParameterServiceBean.java index c44874111..424a05386 100644 --- a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/interaction/slash/parameter/SlashCommandParameterServiceBean.java +++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/interaction/slash/parameter/SlashCommandParameterServiceBean.java @@ -5,6 +5,7 @@ import dev.sheldan.abstracto.core.interaction.slash.parameter.provider.SlashComm import dev.sheldan.abstracto.core.exception.AbstractoRunTimeException; import dev.sheldan.abstracto.core.models.database.AEmote; import dev.sheldan.abstracto.core.service.EmoteService; +import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.User; import net.dv8tion.jda.api.entities.emoji.Emoji; import net.dv8tion.jda.api.interactions.commands.CommandInteractionPayload; @@ -138,16 +139,16 @@ public class SlashCommandParameterServiceBean implements SlashCommandParameterSe } @Override - public AEmote loadAEmoteFromString(String input, CommandInteractionPayload event) { - Emoji emoji = loadEmoteFromString(input, event); + public AEmote loadAEmoteFromString(String input, Guild guild) { + Emoji emoji = loadEmoteFromString(input, guild); return emoteService.getFakeEmoteFromEmoji(emoji); } @Override - public Emoji loadEmoteFromString(String input, CommandInteractionPayload event) { + public Emoji loadEmoteFromString(String input, Guild guild) { if(StringUtils.isNumeric(input)) { long emoteId = Long.parseLong(input); - return event.getGuild().getEmojiById(emoteId); + return guild.getEmojiById(emoteId); } return Emoji.fromFormatted(input); } diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/interaction/slash/payload/SlashCommandConfirmationPayload.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/interaction/slash/payload/SlashCommandConfirmationPayload.java new file mode 100644 index 000000000..def00bd8e --- /dev/null +++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/interaction/slash/payload/SlashCommandConfirmationPayload.java @@ -0,0 +1,17 @@ +package dev.sheldan.abstracto.core.interaction.slash.payload; + +import dev.sheldan.abstracto.core.interaction.button.ButtonPayload; +import lombok.Builder; +import lombok.Getter; + +@Builder +@Getter +public class SlashCommandConfirmationPayload implements ButtonPayload { + + private CommandConfirmationAction action; + private Long interactionId; + + public enum CommandConfirmationAction { + CONFIRM, ABORT + } +} diff --git a/abstracto-application/core/core-int/src/main/java/dev/sheldan/abstracto/core/interaction/InteractionService.java b/abstracto-application/core/core-int/src/main/java/dev/sheldan/abstracto/core/interaction/InteractionService.java index 07a241400..154631966 100644 --- a/abstracto-application/core/core-int/src/main/java/dev/sheldan/abstracto/core/interaction/InteractionService.java +++ b/abstracto-application/core/core-int/src/main/java/dev/sheldan/abstracto/core/interaction/InteractionService.java @@ -15,7 +15,8 @@ public interface InteractionService { CompletableFuture replyString(String text, IReplyCallback callback); CompletableFuture replyEmbed(String templateKey, IReplyCallback callback); List> sendEmbed(String templateKey, InteractionHook interactionHook); - CompletableFuture editOriginal(MessageToSend messageToSend, InteractionHook interactionHook); + CompletableFuture replaceOriginal(MessageToSend messageToSend, InteractionHook interactionHook); + CompletableFuture replaceOriginal(String template, Object model, InteractionHook interactionHook); CompletableFuture replyMessageToSend(MessageToSend messageToSend, IReplyCallback callback); CompletableFuture replyMessage(String templateKey, Object model, IReplyCallback callback); CompletableFuture replyString(String content, InteractionHook interactionHook); diff --git a/abstracto-application/core/core-int/src/main/java/dev/sheldan/abstracto/core/interaction/slash/SlashCommandService.java b/abstracto-application/core/core-int/src/main/java/dev/sheldan/abstracto/core/interaction/slash/SlashCommandService.java index d6379384f..a08243ddf 100644 --- a/abstracto-application/core/core-int/src/main/java/dev/sheldan/abstracto/core/interaction/slash/SlashCommandService.java +++ b/abstracto-application/core/core-int/src/main/java/dev/sheldan/abstracto/core/interaction/slash/SlashCommandService.java @@ -1,7 +1,9 @@ package dev.sheldan.abstracto.core.interaction.slash; import dev.sheldan.abstracto.core.command.config.CommandConfiguration; +import dev.sheldan.abstracto.core.command.execution.CommandResult; import net.dv8tion.jda.api.entities.Guild; +import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import net.dv8tion.jda.api.interactions.commands.Command; import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData; import org.springframework.data.util.Pair; @@ -16,4 +18,6 @@ public interface SlashCommandService { CompletableFuture deleteGuildSlashCommands(Guild guild, List slashCommandId, List commandInServerIdsToUnset); CompletableFuture addGuildSlashCommands(Guild guild, List, SlashCommandData>> commandData); void storeCreatedSlashCommands(Guild guild, List, SlashCommandData>> commandData, List createdCommands); + CompletableFuture completeConfirmableCommand(SlashCommandInteractionEvent event, String template); + CompletableFuture completeConfirmableCommand(SlashCommandInteractionEvent event, String template, Object parameter); } diff --git a/abstracto-application/core/core-int/src/main/java/dev/sheldan/abstracto/core/interaction/slash/parameter/SlashCommandParameterService.java b/abstracto-application/core/core-int/src/main/java/dev/sheldan/abstracto/core/interaction/slash/parameter/SlashCommandParameterService.java index d4564b03c..91b7a5802 100644 --- a/abstracto-application/core/core-int/src/main/java/dev/sheldan/abstracto/core/interaction/slash/parameter/SlashCommandParameterService.java +++ b/abstracto-application/core/core-int/src/main/java/dev/sheldan/abstracto/core/interaction/slash/parameter/SlashCommandParameterService.java @@ -3,6 +3,7 @@ package dev.sheldan.abstracto.core.interaction.slash.parameter; import dev.sheldan.abstracto.core.command.config.Parameter; import dev.sheldan.abstracto.core.models.database.AEmote; +import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.emoji.Emoji; import net.dv8tion.jda.api.interactions.commands.CommandInteractionPayload; import net.dv8tion.jda.api.interactions.commands.OptionType; @@ -16,8 +17,8 @@ public interface SlashCommandParameterService { Object getCommandOption(String name, CommandInteractionPayload event); Boolean hasCommandOption(String name, CommandInteractionPayload event); Boolean hasCommandOptionWithFullType(String name, CommandInteractionPayload event, OptionType optionType); - AEmote loadAEmoteFromString(String input, CommandInteractionPayload event); - Emoji loadEmoteFromString(String input, CommandInteractionPayload event); + AEmote loadAEmoteFromString(String input, Guild guild); + Emoji loadEmoteFromString(String input, Guild guild); List getTypesFromParameter(Parameter parameter); List getTypesFromParameter(Class clazz); String getFullQualifiedParameterName(String name, OptionType type);