From 37bf3bb253b5e50a26f8e10755f74dc2f37e04c9 Mon Sep 17 00:00:00 2001 From: Connor Schweighoefer <88111627+SquidXTV@users.noreply.github.com> Date: Sun, 21 Aug 2022 01:26:40 +0200 Subject: [PATCH 01/18] Added missing javadocs (#521) * Added missing javadocs * pr comments --- .../commands/filesharing/FileSharingMessageListener.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/filesharing/FileSharingMessageListener.java b/application/src/main/java/org/togetherjava/tjbot/commands/filesharing/FileSharingMessageListener.java index 1e1c0a3639..1e6e4285b3 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/filesharing/FileSharingMessageListener.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/filesharing/FileSharingMessageListener.java @@ -49,7 +49,12 @@ public class FileSharingMessageListener extends MessageReceiverAdapter { private final Predicate isStagingChannelName; private final Predicate isOverviewChannelName; - + /** + * Creates a new instance. + * + * @param config used to get api key and channel names. + * @see org.togetherjava.tjbot.commands.Features + */ public FileSharingMessageListener(@NotNull Config config) { super(Pattern.compile(".*")); From 9fe79162ac24b29d2fcf5f5831fbdca3301394ee Mon Sep 17 00:00:00 2001 From: AJ <75995313+GeRmAnImAl@users.noreply.github.com> Date: Tue, 23 Aug 2022 12:12:49 -0400 Subject: [PATCH 02/18] Implement thread close reminder for ask threads (#524) Added a short embed message to sendExplanationMessage() in HelpSystemHelper.java for the purpose of reminding users to use the slash command "/close". The command is used to close their question thread when their question has been answered. Addresses issues #482 --- .../togetherjava/tjbot/commands/help/HelpSystemHelper.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/help/HelpSystemHelper.java b/application/src/main/java/org/togetherjava/tjbot/commands/help/HelpSystemHelper.java index e9c00a7460..dc6392fec9 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/help/HelpSystemHelper.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/help/HelpSystemHelper.java @@ -90,7 +90,9 @@ RestAction sendExplanationMessage(@NotNull MessageChannel threadChannel If nobody is calling back, that usually means that your question was **not well asked** and \ hence nobody feels confident enough answering. Try to use your time to elaborate, \ **provide details**, context, more code, examples and maybe some screenshots. \ - With enough info, someone knows the answer for sure.""")); + With enough info, someone knows the answer for sure."""), + HelpSystemHelper.embedWith( + "Don't forget to close your thread using the command **/close** when your question has been answered, thanks.")); MessageAction action = threadChannel.sendMessage(message); if (useCodeSyntaxExampleImage) { From 3683a3b9c39b6895677ae52eb8b32bd3596d46ef Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 24 Aug 2022 09:11:36 +0200 Subject: [PATCH 03/18] Bump com.diffplug.spotless from 6.9.1 to 6.10.0 (#526) Bumps com.diffplug.spotless from 6.9.1 to 6.10.0. --- updated-dependencies: - dependency-name: com.diffplug.spotless dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 2c1bf4ffeb..c0d9a4d04a 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ plugins { id 'java' - id "com.diffplug.spotless" version "6.9.1" + id "com.diffplug.spotless" version "6.10.0" id "org.sonarqube" version "3.4.0.2513" id "name.remal.sonarlint" version "1.5.0" } From 3948e6a3affbf12ddf55148b2e735d5f425210dc Mon Sep 17 00:00:00 2001 From: Tanish Azad <73871477+Taz03@users.noreply.github.com> Date: Wed, 24 Aug 2022 20:25:11 +0530 Subject: [PATCH 04/18] Replaces enum-utility-pattern classes with regular noninstantiable classes (#529) * Replaces enum-utility-pattern classes with regular noninstantiable classes * removed unused variable --- .../src/main/java/org/togetherjava/tjbot/Application.java | 6 ++++-- .../java/org/togetherjava/tjbot/BootstrapLauncher.java | 6 ++++-- .../java/org/togetherjava/tjbot/commands/Features.java | 6 ++++-- .../mathcommands/wolframalpha/WolframAlphaImages.java | 8 +++++--- .../tjbot/commands/moderation/ModerationUtils.java | 6 ++++-- .../org/togetherjava/tjbot/commands/utils/Hashing.java | 6 ++++-- .../togetherjava/tjbot/commands/utils/MessageUtils.java | 6 ++++-- .../tjbot/commands/utils/StringDistances.java | 6 ++++-- 8 files changed, 33 insertions(+), 17 deletions(-) diff --git a/application/src/main/java/org/togetherjava/tjbot/Application.java b/application/src/main/java/org/togetherjava/tjbot/Application.java index 4dc8ec128b..73b0b6be1a 100644 --- a/application/src/main/java/org/togetherjava/tjbot/Application.java +++ b/application/src/main/java/org/togetherjava/tjbot/Application.java @@ -27,8 +27,10 @@ * New commands can be created by implementing {@link SlashCommandInteractionEvent} or extending * {@link SlashCommandAdapter}. They can then be registered in {@link Features}. */ -public enum Application { - ; +public class Application { + private Application() { + throw new UnsupportedOperationException("Utility class, construction not supported"); + } private static final Logger logger = LoggerFactory.getLogger(Application.class); private static final String DEFAULT_CONFIG_PATH = "config.json"; diff --git a/application/src/main/java/org/togetherjava/tjbot/BootstrapLauncher.java b/application/src/main/java/org/togetherjava/tjbot/BootstrapLauncher.java index 0be2bb353a..bd264ca51e 100644 --- a/application/src/main/java/org/togetherjava/tjbot/BootstrapLauncher.java +++ b/application/src/main/java/org/togetherjava/tjbot/BootstrapLauncher.java @@ -4,8 +4,10 @@ * A bootstrap launcher with minimal dependencies that sets up needed parts and workarounds for the * main logic to take over. */ -public enum BootstrapLauncher { - ; +public class BootstrapLauncher { + private BootstrapLauncher() { + throw new UnsupportedOperationException("Utility class, construction not supported"); + } /** * Starts the main application. diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/Features.java b/application/src/main/java/org/togetherjava/tjbot/commands/Features.java index 81657ce752..83914e83ee 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/Features.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/Features.java @@ -39,8 +39,10 @@ * To add a new slash command, extend the commands returned by * {@link #createFeatures(JDA, Database, Config)}. */ -public enum Features { - ; +public class Features { + private Features() { + throw new UnsupportedOperationException("Utility class, construction not supported"); + } /** * Creates all features that should be registered with this application. diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/WolframAlphaImages.java b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/WolframAlphaImages.java index 00c319ec6f..47a3a9306e 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/WolframAlphaImages.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/WolframAlphaImages.java @@ -25,8 +25,7 @@ * Utility class to work with images returned by the Wolfram Alpha API. For example to render and * combine them. */ -enum WolframAlphaImages { - ; +class WolframAlphaImages { static final String IMAGE_FORMAT = "png"; private static final Color IMAGE_BACKGROUND = Color.WHITE; private static final int IMAGE_MARGIN_PX = 10; @@ -35,7 +34,10 @@ enum WolframAlphaImages { new FontRenderContext(new AffineTransform(), true, true); private static final Color TITLE_COLOR = Color.decode("#3C3C3C"); private static final Font TITLE_FONT = new Font("Arial", Font.BOLD, 15); - private static final int TITLE_HEIGHT_PX = 20; + + private WolframAlphaImages() { + throw new UnsupportedOperationException("Utility class, construction not supported"); + } static @NotNull BufferedImage renderTitle(@NotNull String title) { Rectangle2D titleBounds = TITLE_FONT.getStringBounds(title, TITLE_RENDER_CONTEXT); diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/ModerationUtils.java b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/ModerationUtils.java index 48119596d4..edf554d80c 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/ModerationUtils.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/ModerationUtils.java @@ -21,8 +21,10 @@ /** * Utility class offering helpers revolving around user moderation, such as banning or kicking. */ -public enum ModerationUtils { - ; +public class ModerationUtils { + private ModerationUtils() { + throw new UnsupportedOperationException("Utility class, construction not supported"); + } private static final Logger logger = LoggerFactory.getLogger(ModerationUtils.class); /** diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/utils/Hashing.java b/application/src/main/java/org/togetherjava/tjbot/commands/utils/Hashing.java index c82ed92bfe..b019c13864 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/utils/Hashing.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/utils/Hashing.java @@ -10,8 +10,10 @@ /** * Utility for hashing data. */ -public enum Hashing { - ; +public class Hashing { + private Hashing() { + throw new UnsupportedOperationException("Utility class, construction not supported"); + } /** * All characters available in the hexadecimal-system, as UTF-8 encoded array. diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/utils/MessageUtils.java b/application/src/main/java/org/togetherjava/tjbot/commands/utils/MessageUtils.java index 9ef485e9f2..380b2a50b6 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/utils/MessageUtils.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/utils/MessageUtils.java @@ -14,8 +14,10 @@ * This class is meant to contain all utility methods for {@link Message} that can be used on all * other commands to avoid similar methods appearing everywhere. */ -public enum MessageUtils { - ; +public class MessageUtils { + private MessageUtils() { + throw new UnsupportedOperationException("Utility class, construction not supported"); + } /** * Disables all the buttons that a message has. Disabling buttons deems it as not clickable to diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/utils/StringDistances.java b/application/src/main/java/org/togetherjava/tjbot/commands/utils/StringDistances.java index 27adb8457d..79b817d476 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/utils/StringDistances.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/utils/StringDistances.java @@ -11,8 +11,10 @@ /** * Utility class for computing string distances, for example the edit distance between two words. */ -public enum StringDistances { - ; +public class StringDistances { + private StringDistances() { + throw new UnsupportedOperationException("Utility class, construction not supported"); + } /** * Computes the candidate that matches the given query string best. From e0b909c969d9a8c369ad223c539953533550f466 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 25 Aug 2022 08:56:35 +0200 Subject: [PATCH 05/18] Bump ascii-table from 1.3.0 to 1.4.0 (#531) Bumps [ascii-table](https://github.com/freva/ascii-table) from 1.3.0 to 1.4.0. - [Release notes](https://github.com/freva/ascii-table/releases) - [Commits](https://github.com/freva/ascii-table/compare/1.3.0...1.4.0) --- updated-dependencies: - dependency-name: com.github.freva:ascii-table dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- application/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/build.gradle b/application/build.gradle index b239730637..c36956258d 100644 --- a/application/build.gradle +++ b/application/build.gradle @@ -61,7 +61,7 @@ dependencies { implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.13.0' implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.0' - implementation 'com.github.freva:ascii-table:1.3.0' + implementation 'com.github.freva:ascii-table:1.4.0' implementation 'com.github.ben-manes.caffeine:caffeine:3.1.1' From e5100781f3475b517aee63cddc6159a9abef4226 Mon Sep 17 00:00:00 2001 From: Connor Schweighoefer <88111627+SquidXTV@users.noreply.github.com> Date: Thu, 25 Aug 2022 12:32:52 +0200 Subject: [PATCH 06/18] Fixed attachment error + close input stream correctly (#530) * Fixed attachment error + close input stream correctly * Improved log message clarity --- .../filesharing/FileSharingMessageListener.java | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/filesharing/FileSharingMessageListener.java b/application/src/main/java/org/togetherjava/tjbot/commands/filesharing/FileSharingMessageListener.java index 1e6e4285b3..8aa38da231 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/filesharing/FileSharingMessageListener.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/filesharing/FileSharingMessageListener.java @@ -83,11 +83,17 @@ public void onMessageReceived(@NotNull MessageReceivedEvent event) { .filter(this::isAttachmentRelevant) .toList(); + if (attachments.isEmpty()) { + return; + } + CompletableFuture.runAsync(() -> { try { processAttachments(event, attachments); } catch (Exception e) { - LOGGER.error("Unknown error while processing attachments", e); + LOGGER.error( + "Unknown error while processing attachments. Channel: {}, Author: {}, Message ID: {}.", + event.getChannel().getName(), author.getId(), event.getMessageId(), e); } }); } @@ -125,7 +131,7 @@ private void processAttachments(@NotNull MessageReceivedEvent event, } private @NotNull String readAttachment(@NotNull InputStream stream) { - try { + try (stream) { return new String(stream.readAllBytes(), StandardCharsets.UTF_8); } catch (IOException e) { throw new UncheckedIOException(e); @@ -184,7 +190,8 @@ private void processAttachments(@NotNull MessageReceivedEvent event, if (statusCode < HttpURLConnection.HTTP_OK || statusCode >= HttpURLConnection.HTTP_MULT_CHOICE) { - throw new IllegalStateException("Gist API unexpected response: " + apiResponse.body()); + throw new IllegalStateException("Gist API unexpected response: %s. Request JSON: %s" + .formatted(apiResponse.body(), body)); } GistResponse gistResponse; From c63e9c7d258aab8ef971ef75a0158e4e64bea755 Mon Sep 17 00:00:00 2001 From: Daniel Tischner Date: Thu, 25 Aug 2022 14:33:28 +0200 Subject: [PATCH 07/18] Adding 'website', basic spring boot serving static site (#528) --- settings.gradle | 2 ++ website/build.gradle | 27 +++++++++++++++++++ .../tjbot/website/Application.java | 19 +++++++++++++ .../src/main/resources/application.properties | 1 + website/src/main/resources/public/index.html | 12 +++++++++ 5 files changed, 61 insertions(+) create mode 100644 website/build.gradle create mode 100644 website/src/main/java/org/togetherjava/tjbot/website/Application.java create mode 100644 website/src/main/resources/application.properties create mode 100644 website/src/main/resources/public/index.html diff --git a/settings.gradle b/settings.gradle index 811b17ba0f..ebc2eba2d0 100644 --- a/settings.gradle +++ b/settings.gradle @@ -6,3 +6,5 @@ include 'formatter' // NOTE The logviewer does not properly work as of today. // But it is causing major build slowdowns, so we exclude it for the time being. // include 'logviewer' +include 'website' + diff --git a/website/build.gradle b/website/build.gradle new file mode 100644 index 0000000000..c37398427d --- /dev/null +++ b/website/build.gradle @@ -0,0 +1,27 @@ +plugins { + id 'org.springframework.boot' version '2.7.3' + id 'io.spring.dependency-management' version '1.0.13.RELEASE' + id "com.google.cloud.tools.jib" version "3.1.4" + id 'java' +} + +dependencies { + implementation 'org.springframework.boot:spring-boot-starter-web' +} + +var outputImage = 'togetherjava.duckdns.org:5001/togetherjava/website:' + System.getenv('BRANCH_NAME') ?: 'latest' + +jib { + from.image = 'eclipse-temurin:18' + to { + image = outputImage + auth { + username = System.getenv('REGISTRY_USER') ?: '' + password = System.getenv('REGISTRY_PASSWORD') ?: '' + } + } + container { + setPorts(["5051"].asList()) + setCreationTime(Instant.now().toString()) + } +} diff --git a/website/src/main/java/org/togetherjava/tjbot/website/Application.java b/website/src/main/java/org/togetherjava/tjbot/website/Application.java new file mode 100644 index 0000000000..99e2dd2dbb --- /dev/null +++ b/website/src/main/java/org/togetherjava/tjbot/website/Application.java @@ -0,0 +1,19 @@ +package org.togetherjava.tjbot.website; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * Spring boot application to serve the bots welcome webpage. + */ +@SpringBootApplication +public class Application { + /** + * Starts the application. + * + * @param args Not supported + */ + public static void main(final String[] args) { + SpringApplication.run(Application.class, args); + } +} diff --git a/website/src/main/resources/application.properties b/website/src/main/resources/application.properties new file mode 100644 index 0000000000..26d0a1edc8 --- /dev/null +++ b/website/src/main/resources/application.properties @@ -0,0 +1 @@ +server.port=5051 diff --git a/website/src/main/resources/public/index.html b/website/src/main/resources/public/index.html new file mode 100644 index 0000000000..8d5d569277 --- /dev/null +++ b/website/src/main/resources/public/index.html @@ -0,0 +1,12 @@ + + + + + + Together Java + + +

Hello from Together Java

+Visit us at: discord.gg/P3UmanFmvK + + From 292071a45dcde3f26033382b5edd148fc63813a4 Mon Sep 17 00:00:00 2001 From: Tanish Azad <73871477+Taz03@users.noreply.github.com> Date: Thu, 25 Aug 2022 18:16:43 +0530 Subject: [PATCH 08/18] remove unnecessary channel checks (#517) * removed unnecessary checks from AskCommand * removed unnecessary checks from ChangeHelpCategoryCommand * removed unnecessary checks from ChangeHelpTitleCommand * removed unnecessary checks from CloseCommand * removed unused code from HelpSystemHelper * removed unnecessary checks from AuditCommand * remove unnecessary checks from ModerationUtils#handleRoleChangeChecks * remove unused imports * removed unnecessary checks from BanCommand * removed unnecessary checks from KickCommand * removed unnecessary checks from NoteCommand * removed unnecessary checks from UnbanCommand * removed unnecessary checks from WarnCommand * removed unused method from MoerationUtils * removed unnecessary checks from TagManageCommand * removed unnecessary checks from TopHelpersCommand * removed unused entry from comfig template * fixed tag manage command test * fixed TagManagedCommandTest * restore stagingChannelPattern * fixed the logic of AuditCommand checks --- .../togetherjava/tjbot/commands/Features.java | 18 +++++----- .../tjbot/commands/help/AskCommand.java | 16 --------- .../help/ChangeHelpCategoryCommand.java | 4 --- .../commands/help/ChangeHelpTitleCommand.java | 2 +- .../tjbot/commands/help/CloseCommand.java | 11 +----- .../tjbot/commands/help/HelpSystemHelper.java | 18 ---------- .../commands/moderation/AuditCommand.java | 16 +++------ .../tjbot/commands/moderation/BanCommand.java | 12 +------ .../commands/moderation/KickCommand.java | 12 +------ .../commands/moderation/ModerationUtils.java | 35 +------------------ .../commands/moderation/MuteCommand.java | 6 +--- .../commands/moderation/NoteCommand.java | 13 +------ .../moderation/QuarantineCommand.java | 6 +--- .../commands/moderation/UnbanCommand.java | 12 +------ .../commands/moderation/UnmuteCommand.java | 6 +--- .../moderation/UnquarantineCommand.java | 6 +--- .../commands/moderation/WarnCommand.java | 13 +------ .../tjbot/commands/tags/TagManageCommand.java | 21 +---------- .../commands/tophelper/TopHelpersCommand.java | 24 +------------ .../commands/tags/TagManageCommandTest.java | 16 +-------- 20 files changed, 28 insertions(+), 239 deletions(-) diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/Features.java b/application/src/main/java/org/togetherjava/tjbot/commands/Features.java index 83914e83ee..0ce308e748 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/Features.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/Features.java @@ -94,26 +94,26 @@ private Features() { features.add(new PingCommand()); features.add(new TeXCommand()); features.add(new TagCommand(tagSystem)); - features.add(new TagManageCommand(tagSystem, config, modAuditLogWriter)); + features.add(new TagManageCommand(tagSystem, modAuditLogWriter)); features.add(new TagsCommand(tagSystem)); features.add(new VcActivityCommand()); - features.add(new WarnCommand(actionsStore, config)); - features.add(new KickCommand(actionsStore, config)); - features.add(new BanCommand(actionsStore, config)); - features.add(new UnbanCommand(actionsStore, config)); - features.add(new AuditCommand(actionsStore, config)); + features.add(new WarnCommand(actionsStore)); + features.add(new KickCommand(actionsStore)); + features.add(new BanCommand(actionsStore)); + features.add(new UnbanCommand(actionsStore)); + features.add(new AuditCommand(actionsStore)); features.add(new MuteCommand(actionsStore, config)); features.add(new UnmuteCommand(actionsStore, config)); - features.add(new TopHelpersCommand(database, config)); + features.add(new TopHelpersCommand(database)); features.add(new RoleSelectCommand()); - features.add(new NoteCommand(actionsStore, config)); + features.add(new NoteCommand(actionsStore)); features.add(new RemindCommand(database)); features.add(new QuarantineCommand(actionsStore, config)); features.add(new UnquarantineCommand(actionsStore, config)); features.add(new WhoIsCommand()); features.add(new WolframAlphaCommand(config)); features.add(new AskCommand(config, helpSystemHelper)); - features.add(new CloseCommand(helpSystemHelper)); + features.add(new CloseCommand()); features.add(new ChangeHelpCategoryCommand(config, helpSystemHelper)); features.add(new ChangeHelpTitleCommand(helpSystemHelper)); diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/help/AskCommand.java b/application/src/main/java/org/togetherjava/tjbot/commands/help/AskCommand.java index d29f72807b..c0acedb06c 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/help/AskCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/help/AskCommand.java @@ -77,10 +77,6 @@ public void onSlashCommand(@NotNull SlashCommandInteractionEvent event) { String title = event.getOption(TITLE_OPTION).getAsString(); String category = event.getOption(CATEGORY_OPTION).getAsString(); - if (!handleIsStagingChannel(event)) { - return; - } - if (!handleIsValidTitle(title, event)) { return; } @@ -107,18 +103,6 @@ public void onSlashCommand(@NotNull SlashCommandInteractionEvent event) { }, e -> handleFailure(e, eventHook)); } - private boolean handleIsStagingChannel(@NotNull IReplyCallback event) { - if (helper.isStagingChannelName(event.getChannel().getName())) { - return true; - } - - event.reply("Sorry, but this command can only be used in the help staging channel.") - .setEphemeral(true) - .queue(); - - return false; - } - private boolean handleIsValidTitle(@NotNull CharSequence title, @NotNull IReplyCallback event) { if (HelpSystemHelper.isTitleValid(title)) { return true; diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/help/ChangeHelpCategoryCommand.java b/application/src/main/java/org/togetherjava/tjbot/commands/help/ChangeHelpCategoryCommand.java index 576890ecb3..9bf24e54f0 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/help/ChangeHelpCategoryCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/help/ChangeHelpCategoryCommand.java @@ -71,10 +71,6 @@ public ChangeHelpCategoryCommand(@NotNull Config config, @NotNull HelpSystemHelp public void onSlashCommand(@NotNull SlashCommandInteractionEvent event) { String category = event.getOption(CATEGORY_OPTION).getAsString(); - if (!helper.handleIsHelpThread(event)) { - return; - } - ThreadChannel helpThread = event.getThreadChannel(); if (helpThread.isArchived()) { event.reply("This thread is already closed.").setEphemeral(true).queue(); diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/help/ChangeHelpTitleCommand.java b/application/src/main/java/org/togetherjava/tjbot/commands/help/ChangeHelpTitleCommand.java index 14a6d0cb5b..5090f26d07 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/help/ChangeHelpTitleCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/help/ChangeHelpTitleCommand.java @@ -58,7 +58,7 @@ public ChangeHelpTitleCommand(@NotNull HelpSystemHelper helper) { public void onSlashCommand(@NotNull SlashCommandInteractionEvent event) { String title = event.getOption(TITLE_OPTION).getAsString(); - if (!helper.handleIsHelpThread(event) || !handleIsValidTitle(title, event)) { + if (!handleIsValidTitle(title, event)) { return; } diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/help/CloseCommand.java b/application/src/main/java/org/togetherjava/tjbot/commands/help/CloseCommand.java index 14106631ee..437e75131c 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/help/CloseCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/help/CloseCommand.java @@ -26,31 +26,22 @@ public final class CloseCommand extends SlashCommandAdapter { private static final int COOLDOWN_DURATION_VALUE = 30; private static final ChronoUnit COOLDOWN_DURATION_UNIT = ChronoUnit.MINUTES; - private final HelpSystemHelper helper; private final Cache helpThreadIdToLastClose; /** * Creates a new instance. - * - * @param helper the helper to use */ - public CloseCommand(@NotNull HelpSystemHelper helper) { + public CloseCommand() { super("close", "Close this question thread", SlashCommandVisibility.GUILD); helpThreadIdToLastClose = Caffeine.newBuilder() .maximumSize(1_000) .expireAfterAccess(COOLDOWN_DURATION_VALUE, TimeUnit.of(COOLDOWN_DURATION_UNIT)) .build(); - - this.helper = helper; } @Override public void onSlashCommand(@NotNull SlashCommandInteractionEvent event) { - if (!helper.handleIsHelpThread(event)) { - return; - } - ThreadChannel helpThread = event.getThreadChannel(); if (helpThread.isArchived()) { event.reply("This thread is already closed.").setEphemeral(true).queue(); diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/help/HelpSystemHelper.java b/application/src/main/java/org/togetherjava/tjbot/commands/help/HelpSystemHelper.java index dc6392fec9..f5dfa9733b 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/help/HelpSystemHelper.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/help/HelpSystemHelper.java @@ -2,7 +2,6 @@ import net.dv8tion.jda.api.EmbedBuilder; import net.dv8tion.jda.api.entities.*; -import net.dv8tion.jda.api.interactions.callbacks.IReplyCallback; import net.dv8tion.jda.api.requests.RestAction; import net.dv8tion.jda.api.requests.restaction.MessageAction; import net.dv8tion.jda.internal.requests.CompletedRestAction; @@ -49,7 +48,6 @@ public final class HelpSystemHelper { private final String stagingChannelPattern; private final String categoryRoleSuffix; - /** * Creates a new instance. * @@ -113,22 +111,6 @@ RestAction sendExplanationMessage(@NotNull MessageChannel threadChannel .build(); } - boolean handleIsHelpThread(@NotNull IReplyCallback event) { - if (event.getChannelType() == ChannelType.GUILD_PUBLIC_THREAD) { - ThreadChannel thread = event.getThreadChannel(); - - if (isOverviewChannelName.test(thread.getParentChannel().getName())) { - return true; - } - } - - event.reply("Sorry, but this command can only be used in a help thread.") - .setEphemeral(true) - .queue(); - - return false; - } - @NotNull Optional handleFindRoleForCategory(@NotNull String category, @NotNull Guild guild) { String roleName = category + categoryRoleSuffix; diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/AuditCommand.java b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/AuditCommand.java index 6097286232..0ff92a1a0b 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/AuditCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/AuditCommand.java @@ -18,14 +18,11 @@ import org.togetherjava.tjbot.commands.SlashCommandAdapter; import org.togetherjava.tjbot.commands.SlashCommandVisibility; -import org.togetherjava.tjbot.config.Config; import java.time.Instant; import java.time.ZoneOffset; import java.util.*; import java.util.function.Function; -import java.util.function.Predicate; -import java.util.regex.Pattern; import java.util.stream.Collectors; /** @@ -42,24 +39,20 @@ public final class AuditCommand extends SlashCommandAdapter { private static final int MAX_PAGE_LENGTH = 25; private static final String PREVIOUS_BUTTON_LABEL = "⬅"; private static final String NEXT_BUTTON_LABEL = "➡"; - private final Predicate hasRequiredRole; private final ModerationActionsStore actionsStore; /** * Constructs an instance. * * @param actionsStore used to store actions issued by this command - * @param config the config to use for this */ - public AuditCommand(@NotNull ModerationActionsStore actionsStore, @NotNull Config config) { + public AuditCommand(@NotNull ModerationActionsStore actionsStore) { super(COMMAND_NAME, "Lists all moderation actions that have been taken against a user", SlashCommandVisibility.GUILD); getData().addOption(OptionType.USER, TARGET_OPTION, "The user who to retrieve actions for", true); - hasRequiredRole = - Pattern.compile(config.getHeavyModerationRolePattern()).asMatchPredicate(); this.actionsStore = Objects.requireNonNull(actionsStore); } @@ -144,11 +137,10 @@ public void onSlashCommand(@NotNull SlashCommandInteractionEvent event) { private boolean handleChecks(@NotNull Member bot, @NotNull Member author, @Nullable Member target, @NotNull IReplyCallback event) { // Member doesn't exist if attempting to audit a user who is not part of the guild. - if (target != null && !ModerationUtils.handleCanInteractWithTarget(ACTION_VERB, bot, author, - target, event)) { - return false; + if (target == null) { + return true; } - return ModerationUtils.handleHasAuthorRole(ACTION_VERB, hasRequiredRole, author, event); + return ModerationUtils.handleCanInteractWithTarget(ACTION_VERB, bot, author, target, event); } private @NotNull List> groupActionsByPages( diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/BanCommand.java b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/BanCommand.java index 109b1275bb..b59450c4ad 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/BanCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/BanCommand.java @@ -20,14 +20,11 @@ import org.slf4j.LoggerFactory; import org.togetherjava.tjbot.commands.SlashCommandAdapter; import org.togetherjava.tjbot.commands.SlashCommandVisibility; -import org.togetherjava.tjbot.config.Config; import java.time.Instant; import java.util.List; import java.util.Objects; import java.util.Optional; -import java.util.function.Predicate; -import java.util.regex.Pattern; /** * This command can ban users and optionally remove their messages from the past days. Banning can @@ -48,16 +45,14 @@ public final class BanCommand extends SlashCommandAdapter { @SuppressWarnings("StaticCollection") private static final List DURATIONS = List.of(ModerationUtils.PERMANENT_DURATION, "1 hour", "3 hours", "1 day", "2 days", "3 days", "7 days", "30 days"); - private final Predicate hasRequiredRole; private final ModerationActionsStore actionsStore; /** * Constructs an instance. * * @param actionsStore used to store actions issued by this command - * @param config the config to use for this */ - public BanCommand(@NotNull ModerationActionsStore actionsStore, @NotNull Config config) { + public BanCommand(@NotNull ModerationActionsStore actionsStore) { super(COMMAND_NAME, "Bans the given user from the server", SlashCommandVisibility.GUILD); OptionData durationData = new OptionData(OptionType.STRING, DURATION_OPTION, @@ -71,8 +66,6 @@ public BanCommand(@NotNull ModerationActionsStore actionsStore, @NotNull Config "the amount of days of the message history to delete, none means no messages are deleted.", true).addChoice("none", 0).addChoice("recent", 1).addChoice("all", 7)); - hasRequiredRole = - Pattern.compile(config.getHeavyModerationRolePattern()).asMatchPredicate(); this.actionsStore = Objects.requireNonNull(actionsStore); } @@ -182,9 +175,6 @@ private boolean handleChecks(@NotNull Member bot, @NotNull Member author, target, event)) { return false; } - if (!ModerationUtils.handleHasAuthorRole(ACTION_VERB, hasRequiredRole, author, event)) { - return false; - } if (!ModerationUtils.handleHasBotPermissions(ACTION_VERB, Permission.BAN_MEMBERS, bot, guild, event)) { return false; diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/KickCommand.java b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/KickCommand.java index 17bd9cf460..8a079587f9 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/KickCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/KickCommand.java @@ -18,12 +18,8 @@ import org.slf4j.LoggerFactory; import org.togetherjava.tjbot.commands.SlashCommandAdapter; import org.togetherjava.tjbot.commands.SlashCommandVisibility; -import org.togetherjava.tjbot.config.Config; import java.util.Objects; -import java.util.function.Predicate; -import java.util.regex.Pattern; - /** * This command can kicks users. Kicking can also be paired with a kick reason. The command will @@ -38,22 +34,19 @@ public final class KickCommand extends SlashCommandAdapter { private static final String REASON_OPTION = "reason"; private static final String COMMAND_NAME = "kick"; private static final String ACTION_VERB = "kick"; - private final Predicate hasRequiredRole; private final ModerationActionsStore actionsStore; /** * Constructs an instance. * * @param actionsStore used to store actions issued by this command - * @param config the config to use for this */ - public KickCommand(@NotNull ModerationActionsStore actionsStore, @NotNull Config config) { + public KickCommand(@NotNull ModerationActionsStore actionsStore) { super(COMMAND_NAME, "Kicks the given user from the server", SlashCommandVisibility.GUILD); getData().addOption(OptionType.USER, TARGET_OPTION, "The user who you want to kick", true) .addOption(OptionType.STRING, REASON_OPTION, "Why the user should be kicked", true); - hasRequiredRole = Pattern.compile(config.getSoftModerationRolePattern()).asMatchPredicate(); this.actionsStore = Objects.requireNonNull(actionsStore); } @@ -123,9 +116,6 @@ private boolean handleChecks(@NotNull Member bot, @NotNull Member author, if (!ModerationUtils.handleCanInteractWithTarget(ACTION_VERB, bot, author, target, event)) { return false; } - if (!ModerationUtils.handleHasAuthorRole(ACTION_VERB, hasRequiredRole, author, event)) { - return false; - } if (!ModerationUtils.handleHasBotPermissions(ACTION_VERB, Permission.KICK_MEMBERS, bot, guild, event)) { return false; diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/ModerationUtils.java b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/ModerationUtils.java index edf554d80c..0450e92c49 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/ModerationUtils.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/ModerationUtils.java @@ -187,7 +187,6 @@ private static void handleAbsentTarget(@NotNull String actionVerb, *
  • the target is not member of the guild
  • *
  • the bot or author do not have enough permissions to interact with the target
  • *
  • the bot or author do not have enough permissions to interact with the role
  • - *
  • the author does not have the required role for this interaction
  • *
  • the bot does not have the MANAGE_ROLES permission
  • *
  • the given reason is too long
  • * @@ -199,7 +198,6 @@ private static void handleAbsentTarget(@NotNull String actionVerb, * @param bot the bot executing this interaction * @param author the author attempting to interact with the target * @param guild the guild this interaction is executed on - * @param hasRequiredRole a predicate used to identify required roles by their name * @param reason the reason for this interaction * @param event the event used to respond to the user * @return Whether the bot and the author have enough permission @@ -208,8 +206,7 @@ private static void handleAbsentTarget(@NotNull String actionVerb, "squid:S107"}) static boolean handleRoleChangeChecks(@Nullable Role role, @NotNull String actionVerb, @Nullable Member target, @NotNull Member bot, @NotNull Member author, - @NotNull Guild guild, @NotNull Predicate hasRequiredRole, - @NotNull CharSequence reason, @NotNull IReplyCallback event) { + @NotNull Guild guild, @NotNull CharSequence reason, @NotNull IReplyCallback event) { if (role == null) { event .reply("Can not %s the user, unable to find the corresponding role on this server" @@ -233,9 +230,6 @@ static boolean handleRoleChangeChecks(@Nullable Role role, @NotNull String actio if (!handleCanInteractWithRole(bot, author, role, event)) { return false; } - if (!handleHasAuthorRole(actionVerb, hasRequiredRole, author, event)) { - return false; - } if (!handleHasBotPermissions(actionVerb, Permission.MANAGE_ROLES, bot, guild, event)) { return false; } @@ -269,33 +263,6 @@ static boolean handleHasAuthorPermissions(@NotNull String actionVerb, return true; } - /** - * Checks whether the given bot has enough permission to execute the given action. For example - * whether it has enough permissions to ban users. - *

    - * If not, it will handle the situation and respond to the user. - * - * @param actionVerb the interaction as verb, for example {@code "ban"} or {@code "kick"} - * @param hasRequiredRole a predicate used to identify required roles by their name - * @param author the author attempting to interact with the target - * @param event the event used to respond to the user - * @return Whether the bot has the required permission - */ - @SuppressWarnings("BooleanMethodNameMustStartWithQuestion") - static boolean handleHasAuthorRole(@NotNull String actionVerb, - @NotNull Predicate hasRequiredRole, @NotNull Member author, - @NotNull IReplyCallback event) { - if (author.getRoles().stream().map(Role::getName).anyMatch(hasRequiredRole)) { - return true; - } - event - .reply("You can not %s users in this guild since you do not have the required role." - .formatted(actionVerb)) - .setEphemeral(true) - .queue(); - return false; - } - /** * Creates a message to be displayed as response to a moderation action. *

    diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/MuteCommand.java b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/MuteCommand.java index 7b4f244ef1..4c9e9622b7 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/MuteCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/MuteCommand.java @@ -21,8 +21,6 @@ import java.util.List; import java.util.Objects; import java.util.Optional; -import java.util.function.Predicate; -import java.util.regex.Pattern; /** * This command can mute users. Muting can also be paired with a reason. The command will also try @@ -41,7 +39,6 @@ public final class MuteCommand extends SlashCommandAdapter { @SuppressWarnings("StaticCollection") private static final List DURATIONS = List.of("10 minutes", "30 minutes", "1 hour", "3 hours", "1 day", "3 days", "7 days", ModerationUtils.PERMANENT_DURATION); - private final Predicate hasRequiredRole; private final ModerationActionsStore actionsStore; private final Config config; @@ -64,7 +61,6 @@ public MuteCommand(@NotNull ModerationActionsStore actionsStore, @NotNull Config .addOption(OptionType.STRING, REASON_OPTION, "Why the user should be muted", true); this.config = config; - hasRequiredRole = Pattern.compile(config.getSoftModerationRolePattern()).asMatchPredicate(); this.actionsStore = Objects.requireNonNull(actionsStore); } @@ -141,7 +137,7 @@ private boolean handleChecks(@NotNull Member bot, @NotNull Member author, @NotNull IReplyCallback event) { if (!ModerationUtils.handleRoleChangeChecks( ModerationUtils.getMutedRole(guild, config).orElse(null), ACTION_VERB, target, bot, - author, guild, hasRequiredRole, reason, event)) { + author, guild, reason, event)) { return false; } if (Objects.requireNonNull(target) diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/NoteCommand.java b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/NoteCommand.java index 285e4973ca..54bcc11426 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/NoteCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/NoteCommand.java @@ -11,12 +11,8 @@ import org.slf4j.LoggerFactory; import org.togetherjava.tjbot.commands.SlashCommandAdapter; import org.togetherjava.tjbot.commands.SlashCommandVisibility; -import org.togetherjava.tjbot.config.Config; import java.util.Objects; -import java.util.function.Predicate; -import java.util.regex.Pattern; - /** * This command allows users to write notes about others. Notes are persisted and can be retrieved @@ -32,15 +28,13 @@ public final class NoteCommand extends SlashCommandAdapter { private static final String CONTENT_OPTION = "content"; private static final String ACTION_VERB = "write a note about"; private final ModerationActionsStore actionsStore; - private final Predicate hasRequiredRole; /** * Creates a new instance. * * @param actionsStore used to store actions issued by this command - * @param config the config to use for this */ - public NoteCommand(@NotNull ModerationActionsStore actionsStore, @NotNull Config config) { + public NoteCommand(@NotNull ModerationActionsStore actionsStore) { super("note", "Writes a note about the given user", SlashCommandVisibility.GUILD); getData() @@ -49,7 +43,6 @@ public NoteCommand(@NotNull ModerationActionsStore actionsStore, @NotNull Config .addOption(OptionType.STRING, CONTENT_OPTION, "The content of the note you want to write", true); - hasRequiredRole = Pattern.compile(config.getSoftModerationRolePattern()).asMatchPredicate(); this.actionsStore = Objects.requireNonNull(actionsStore); } @@ -76,10 +69,6 @@ private boolean handleChecks(@NotNull Member bot, @NotNull Member author, return false; } - if (!ModerationUtils.handleHasAuthorRole(ACTION_VERB, hasRequiredRole, author, event)) { - return false; - } - return ModerationUtils.handleReason(content, event); } diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/QuarantineCommand.java b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/QuarantineCommand.java index cc3453dff5..3e845c643a 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/QuarantineCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/QuarantineCommand.java @@ -17,8 +17,6 @@ import org.togetherjava.tjbot.config.Config; import java.util.Objects; -import java.util.function.Predicate; -import java.util.regex.Pattern; /** * This command can quarantine users. Quarantining can also be paired with a reason. The command @@ -34,7 +32,6 @@ public final class QuarantineCommand extends SlashCommandAdapter { private static final String REASON_OPTION = "reason"; private static final String COMMAND_NAME = "quarantine"; private static final String ACTION_VERB = "quarantine"; - private final Predicate hasRequiredRole; private final ModerationActionsStore actionsStore; private final Config config; @@ -55,7 +52,6 @@ public QuarantineCommand(@NotNull ModerationActionsStore actionsStore, @NotNull true); this.config = config; - hasRequiredRole = Pattern.compile(config.getSoftModerationRolePattern()).asMatchPredicate(); this.actionsStore = Objects.requireNonNull(actionsStore); } @@ -123,7 +119,7 @@ private boolean handleChecks(@NotNull Member bot, @NotNull Member author, @NotNull IReplyCallback event) { if (!ModerationUtils.handleRoleChangeChecks( ModerationUtils.getQuarantinedRole(guild, config).orElse(null), ACTION_VERB, target, - bot, author, guild, hasRequiredRole, reason, event)) { + bot, author, guild, reason, event)) { return false; } diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/UnbanCommand.java b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/UnbanCommand.java index c26670f831..654ac014e9 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/UnbanCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/UnbanCommand.java @@ -12,11 +12,8 @@ import org.slf4j.LoggerFactory; import org.togetherjava.tjbot.commands.SlashCommandAdapter; import org.togetherjava.tjbot.commands.SlashCommandVisibility; -import org.togetherjava.tjbot.config.Config; import java.util.Objects; -import java.util.function.Predicate; -import java.util.regex.Pattern; /** * Unbans a given user. Unbanning can also be paired with a reason. The command fails if the user is @@ -28,16 +25,14 @@ public final class UnbanCommand extends SlashCommandAdapter { private static final String REASON_OPTION = "reason"; private static final String COMMAND_NAME = "unban"; private static final String ACTION_VERB = "unban"; - private final Predicate hasRequiredRole; private final ModerationActionsStore actionsStore; /** * Constructs an instance. * * @param actionsStore used to store actions issued by this command - * @param config the config to use for this */ - public UnbanCommand(@NotNull ModerationActionsStore actionsStore, @NotNull Config config) { + public UnbanCommand(@NotNull ModerationActionsStore actionsStore) { super(COMMAND_NAME, "Unbans the given user from the server", SlashCommandVisibility.GUILD); getData() @@ -45,8 +40,6 @@ public UnbanCommand(@NotNull ModerationActionsStore actionsStore, @NotNull Confi true) .addOption(OptionType.STRING, REASON_OPTION, "Why the user should be unbanned", true); - hasRequiredRole = - Pattern.compile(config.getHeavyModerationRolePattern()).asMatchPredicate(); this.actionsStore = Objects.requireNonNull(actionsStore); } @@ -92,9 +85,6 @@ private static void handleFailure(@NotNull Throwable unbanFailure, @NotNull User @SuppressWarnings({"BooleanMethodNameMustStartWithQuestion"}) private boolean handleChecks(@NotNull IPermissionHolder bot, @NotNull Member author, @NotNull CharSequence reason, @NotNull Guild guild, @NotNull IReplyCallback event) { - if (!ModerationUtils.handleHasAuthorRole(ACTION_VERB, hasRequiredRole, author, event)) { - return false; - } if (!ModerationUtils.handleHasBotPermissions(ACTION_VERB, Permission.BAN_MEMBERS, bot, guild, event)) { return false; diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/UnmuteCommand.java b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/UnmuteCommand.java index bd2e79bdd7..d2b3041660 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/UnmuteCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/UnmuteCommand.java @@ -17,8 +17,6 @@ import org.togetherjava.tjbot.config.Config; import java.util.Objects; -import java.util.function.Predicate; -import java.util.regex.Pattern; /** * This command can unmute muted users. Unmuting can also be paired with a reason. The command will @@ -33,7 +31,6 @@ public final class UnmuteCommand extends SlashCommandAdapter { private static final String REASON_OPTION = "reason"; private static final String COMMAND_NAME = "unmute"; private static final String ACTION_VERB = "unmute"; - private final Predicate hasRequiredRole; private final ModerationActionsStore actionsStore; private final Config config; @@ -52,7 +49,6 @@ public UnmuteCommand(@NotNull ModerationActionsStore actionsStore, @NotNull Conf .addOption(OptionType.STRING, REASON_OPTION, "Why the user should be unmuted", true); this.config = config; - hasRequiredRole = Pattern.compile(config.getSoftModerationRolePattern()).asMatchPredicate(); this.actionsStore = Objects.requireNonNull(actionsStore); } @@ -115,7 +111,7 @@ private boolean handleChecks(@NotNull Member bot, @NotNull Member author, @NotNull IReplyCallback event) { if (!ModerationUtils.handleRoleChangeChecks( ModerationUtils.getMutedRole(guild, config).orElse(null), ACTION_VERB, target, bot, - author, guild, hasRequiredRole, reason, event)) { + author, guild, reason, event)) { return false; } if (Objects.requireNonNull(target) diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/UnquarantineCommand.java b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/UnquarantineCommand.java index 9e33c4841f..c04b173f96 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/UnquarantineCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/UnquarantineCommand.java @@ -17,8 +17,6 @@ import org.togetherjava.tjbot.config.Config; import java.util.Objects; -import java.util.function.Predicate; -import java.util.regex.Pattern; /** * This command can unquarantine quarantined users. Unquarantining can also be paired with a reason. @@ -34,7 +32,6 @@ public final class UnquarantineCommand extends SlashCommandAdapter { private static final String REASON_OPTION = "reason"; private static final String COMMAND_NAME = "unquarantine"; private static final String ACTION_VERB = "unquarantine"; - private final Predicate hasRequiredRole; private final ModerationActionsStore actionsStore; private final Config config; @@ -57,7 +54,6 @@ public UnquarantineCommand(@NotNull ModerationActionsStore actionsStore, true); this.config = config; - hasRequiredRole = Pattern.compile(config.getSoftModerationRolePattern()).asMatchPredicate(); this.actionsStore = Objects.requireNonNull(actionsStore); } @@ -122,7 +118,7 @@ private boolean handleChecks(@NotNull Member bot, @NotNull Member author, @NotNull IReplyCallback event) { if (!ModerationUtils.handleRoleChangeChecks( ModerationUtils.getQuarantinedRole(guild, config).orElse(null), ACTION_VERB, target, - bot, author, guild, hasRequiredRole, reason, event)) { + bot, author, guild, reason, event)) { return false; } diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/WarnCommand.java b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/WarnCommand.java index 32c84a400d..a29b1ea51d 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/WarnCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/WarnCommand.java @@ -13,12 +13,8 @@ import org.slf4j.LoggerFactory; import org.togetherjava.tjbot.commands.SlashCommandAdapter; import org.togetherjava.tjbot.commands.SlashCommandVisibility; -import org.togetherjava.tjbot.config.Config; import java.util.Objects; -import java.util.function.Predicate; -import java.util.regex.Pattern; - /** * This command can warn users. The command will also try to DM the user to inform them about the @@ -33,22 +29,18 @@ public final class WarnCommand extends SlashCommandAdapter { private static final String REASON_OPTION = "reason"; private static final String ACTION_VERB = "warn"; private final ModerationActionsStore actionsStore; - private final Predicate hasRequiredRole; /** * Creates a new instance. * * @param actionsStore used to store actions issued by this command - * @param config the config to use for this */ - public WarnCommand(@NotNull ModerationActionsStore actionsStore, @NotNull Config config) { + public WarnCommand(@NotNull ModerationActionsStore actionsStore) { super("warn", "Warns the given user", SlashCommandVisibility.GUILD); getData().addOption(OptionType.USER, USER_OPTION, "The user who you want to warn", true) .addOption(OptionType.STRING, REASON_OPTION, "Why you want to warn the user", true); - hasRequiredRole = - Pattern.compile(config.getHeavyModerationRolePattern()).asMatchPredicate(); this.actionsStore = Objects.requireNonNull(actionsStore); } @@ -123,9 +115,6 @@ private boolean handleChecks(@NotNull Member bot, @NotNull Member author, target, event)) { return false; } - if (!ModerationUtils.handleHasAuthorRole(ACTION_VERB, hasRequiredRole, author, event)) { - return false; - } return ModerationUtils.handleReason(reason, event); } } diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/tags/TagManageCommand.java b/application/src/main/java/org/togetherjava/tjbot/commands/tags/TagManageCommand.java index 3eb996a7cb..3cebad8f10 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/tags/TagManageCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/tags/TagManageCommand.java @@ -2,8 +2,6 @@ import net.dv8tion.jda.api.EmbedBuilder; import net.dv8tion.jda.api.entities.Guild; -import net.dv8tion.jda.api.entities.Member; -import net.dv8tion.jda.api.entities.Role; import net.dv8tion.jda.api.entities.User; import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import net.dv8tion.jda.api.exceptions.ErrorResponseException; @@ -18,7 +16,6 @@ import org.slf4j.LoggerFactory; import org.togetherjava.tjbot.commands.SlashCommandAdapter; import org.togetherjava.tjbot.commands.SlashCommandVisibility; -import org.togetherjava.tjbot.config.Config; import org.togetherjava.tjbot.moderation.ModAuditLogWriter; import java.time.temporal.TemporalAccessor; @@ -30,8 +27,6 @@ import java.util.OptionalLong; import java.util.function.BiConsumer; import java.util.function.Consumer; -import java.util.function.Predicate; -import java.util.regex.Pattern; /** * Implements the {@code /tag-manage} command which allows management of tags, such as creating, @@ -67,7 +62,6 @@ public final class TagManageCommand extends SlashCommandAdapter { private static final String UNABLE_TO_GET_CONTENT_MESSAGE = "Was unable to retrieve content"; private final TagSystem tagSystem; - private final Predicate hasRequiredRole; private final ModAuditLogWriter modAuditLogWriter; @@ -75,15 +69,13 @@ public final class TagManageCommand extends SlashCommandAdapter { * Creates a new instance, using the given tag system as base. * * @param tagSystem the system providing the actual tag data - * @param config the config to use for this * @param modAuditLogWriter to log tag changes for audition */ - public TagManageCommand(@NotNull TagSystem tagSystem, @NotNull Config config, + public TagManageCommand(@NotNull TagSystem tagSystem, @NotNull ModAuditLogWriter modAuditLogWriter) { super("tag-manage", "Provides commands to manage all tags", SlashCommandVisibility.GUILD); this.tagSystem = tagSystem; - hasRequiredRole = Pattern.compile(config.getTagManageRolePattern()).asMatchPredicate(); this.modAuditLogWriter = modAuditLogWriter; @@ -152,13 +144,6 @@ private static OptionalLong parseMessageIdAndHandle(@NotNull String messageId, @Override public void onSlashCommand(@NotNull SlashCommandInteractionEvent event) { - if (!hasTagManageRole(Objects.requireNonNull(event.getMember()))) { - event.reply("Tags can only be managed by users with a corresponding role.") - .setEphemeral(true) - .queue(); - return; - } - switch (Subcommand.fromName(event.getSubcommandName())) { case RAW -> rawTag(event); case CREATE -> createTag(event); @@ -399,10 +384,6 @@ private void logAction(@NotNull Subcommand subcommand, @NotNull Guild guild, triggeredAt, guild, attachments.toArray(ModAuditLogWriter.Attachment[]::new)); } - private boolean hasTagManageRole(@NotNull Member member) { - return member.getRoles().stream().map(Role::getName).anyMatch(hasRequiredRole); - } - private enum TagStatus { EXISTS, NOT_EXISTS diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/tophelper/TopHelpersCommand.java b/application/src/main/java/org/togetherjava/tjbot/commands/tophelper/TopHelpersCommand.java index 479b1dae19..f7f6a44e4f 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/tophelper/TopHelpersCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/tophelper/TopHelpersCommand.java @@ -5,10 +5,8 @@ import com.github.freva.asciitable.ColumnData; import com.github.freva.asciitable.HorizontalAlign; import net.dv8tion.jda.api.entities.Member; -import net.dv8tion.jda.api.entities.Role; import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import net.dv8tion.jda.api.interactions.callbacks.IDeferrableCallback; -import net.dv8tion.jda.api.interactions.callbacks.IReplyCallback; import net.dv8tion.jda.api.interactions.commands.OptionMapping; import net.dv8tion.jda.api.interactions.commands.OptionType; import net.dv8tion.jda.api.interactions.commands.build.OptionData; @@ -20,7 +18,6 @@ import org.slf4j.LoggerFactory; import org.togetherjava.tjbot.commands.SlashCommandAdapter; import org.togetherjava.tjbot.commands.SlashCommandVisibility; -import org.togetherjava.tjbot.config.Config; import org.togetherjava.tjbot.db.Database; import java.math.BigDecimal; @@ -29,8 +26,6 @@ import java.util.*; import java.util.function.Function; import java.util.function.IntFunction; -import java.util.function.Predicate; -import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.IntStream; @@ -49,15 +44,13 @@ public final class TopHelpersCommand extends SlashCommandAdapter { private static final int TOP_HELPER_LIMIT = 20; private final Database database; - private final Predicate hasRequiredRole; /** * Creates a new instance. * * @param database the database containing the message records of top helpers - * @param config the config to use for this */ - public TopHelpersCommand(@NotNull Database database, @NotNull Config config) { + public TopHelpersCommand(@NotNull Database database) { super(COMMAND_NAME, "Lists top helpers for the last month, or a given month", SlashCommandVisibility.GUILD); @@ -68,15 +61,11 @@ public TopHelpersCommand(@NotNull Database database, @NotNull Config config) { month.getDisplayName(TextStyle.FULL_STANDALONE, Locale.US), month.name())); getData().addOptions(monthData); - hasRequiredRole = Pattern.compile(config.getSoftModerationRolePattern()).asMatchPredicate(); this.database = database; } @Override public void onSlashCommand(@NotNull SlashCommandInteractionEvent event) { - if (!handleHasAuthorRole(event.getMember(), event)) { - return; - } OptionMapping atMonthData = event.getOption(MONTH_OPTION); TimeRange timeRange = computeTimeRange(computeMonth(atMonthData)); @@ -99,17 +88,6 @@ public void onSlashCommand(@NotNull SlashCommandInteractionEvent event) { .onSuccess(members -> handleTopHelpers(topHelpers, members, timeRange, event)); } - @SuppressWarnings("BooleanMethodNameMustStartWithQuestion") - private boolean handleHasAuthorRole(@NotNull Member author, @NotNull IReplyCallback event) { - if (author.getRoles().stream().map(Role::getName).anyMatch(hasRequiredRole)) { - return true; - } - event.reply("You can not compute the top-helpers since you do not have the required role.") - .setEphemeral(true) - .queue(); - return false; - } - private static @NotNull Month computeMonth(@Nullable OptionMapping atMonthData) { if (atMonthData != null) { return Month.valueOf(atMonthData.getAsString()); diff --git a/application/src/test/java/org/togetherjava/tjbot/commands/tags/TagManageCommandTest.java b/application/src/test/java/org/togetherjava/tjbot/commands/tags/TagManageCommandTest.java index 3bec6f8ac3..9f3ddf9d13 100644 --- a/application/src/test/java/org/togetherjava/tjbot/commands/tags/TagManageCommandTest.java +++ b/application/src/test/java/org/togetherjava/tjbot/commands/tags/TagManageCommandTest.java @@ -52,7 +52,7 @@ void setUp() { Database database = Database.createMemoryDatabase(Tags.TAGS); system = spy(new TagSystem(database)); jdaTester = new JdaTester(); - command = new TagManageCommand(system, config, modAuditLogWriter); + command = new TagManageCommand(system, modAuditLogWriter); moderator = jdaTester.createMemberSpy(1); Role moderatorRole = mock(Role.class); @@ -149,20 +149,6 @@ private void failOnRetrieveMessage(@NotNull String messageId, @NotNull Throwable .retrieveMessageById(messageId); } - @Test - @DisplayName("Users without the required role can not use '/tag-manage'") - void commandCanNotBeUsedWithoutRoles() { - // GIVEN a regular user without roles - Member regularUser = jdaTester.createMemberSpy(1); - - // WHEN the regular user triggers any '/tag-manage' command - SlashCommandInteractionEvent event = triggerRawCommandWithUser("foo", regularUser); - - // THEN the command can not be used since the user lacks roles - verify(event).reply("Tags can only be managed by users with a corresponding role."); - verify(modAuditLogWriter, never()).write(any(), any(), any(), any(), any(), any()); - } - @Test @DisplayName("'/tag-manage raw' can not be used on unknown tags") void rawTagCanNotFindUnknownTag() { From b68dcdc536d0436f9d4c653d0768e7507229572e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 26 Aug 2022 08:37:07 +0200 Subject: [PATCH 09/18] Bump flyway-core from 9.1.3 to 9.2.0 (#535) Bumps [flyway-core](https://github.com/flyway/flyway) from 9.1.3 to 9.2.0. - [Release notes](https://github.com/flyway/flyway/releases) - [Commits](https://github.com/flyway/flyway/compare/flyway-9.1.3...flyway-9.2.0) --- updated-dependencies: - dependency-name: org.flywaydb:flyway-core dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- database/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/database/build.gradle b/database/build.gradle index f7a7c2e634..87a33f1b0c 100644 --- a/database/build.gradle +++ b/database/build.gradle @@ -6,7 +6,7 @@ var sqliteVersion = "3.39.2.0" dependencies { implementation "org.xerial:sqlite-jdbc:${sqliteVersion}" - implementation 'org.flywaydb:flyway-core:9.1.3' + implementation 'org.flywaydb:flyway-core:9.2.0' implementation 'org.jooq:jooq:3.17.2' } From d0071b31bc69f1db35119e6ebdf0d3b6d91eb722 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20Radosavljevi=C4=87?= Date: Fri, 26 Aug 2022 07:32:10 +0000 Subject: [PATCH 10/18] Make readme more inviting to beginners (#534) --- README.md | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index cebc069fa1..c6769bd4f8 100644 --- a/README.md +++ b/README.md @@ -9,20 +9,24 @@ [![Reliability Rating](https://sonarcloud.io/api/project_badges/measure?project=Together-Java_TJ-Bot&metric=reliability_rating)](https://sonarcloud.io/dashboard?id=Together-Java_TJ-Bot) [![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=Together-Java_TJ-Bot&metric=security_rating)](https://sonarcloud.io/dashboard?id=Together-Java_TJ-Bot) -TJ-Bot is a Discord Bot used on the [Together Java](https://discord.com/invite/XXFUXzK) server. It is maintained by the community, anyone can contribute. +TJ-Bot is a Discord Bot used on the [Together Java](https://discord.com/invite/XXFUXzK) server. It is maintained by the community, anyone can contribute! ![bot says hello](https://i.imgur.com/FE1MJTV.png) +The idea of this project is to provide learning experience on a real-world project. You will be provided with our guidance at every step of the way, and friendly but thorough reviews! + +Feel free to join our [discord server](https://discord.com/invite/XXFUXzK) if you have any questions, or require assistance with the project. :relaxed: + # Getting started -Please read [Contributing](https://github.com/Together-Java/TJ-Bot/wiki/Contributing) if you want to propose ideas and changes or even implement some yourself. +Please read [Contributing](https://github.com/Together-Java/TJ-Bot/wiki/Contributing) guidelines if you are considering helping us out! +There you will find a detailed guide on how to contribute, and plenty of resources in case this is your first time submitting a PR to an open-source project. Head over to the [Wiki](https://github.com/Together-Java/TJ-Bot/wiki) as general entry point to the project. It provides lots of tutorials, documentation and other information, for example -* creating a discord bot and a private server; -* setting up the project locally; -* adding your own custom commands; -* a technology overview; -* guidance about how to maintain the bot (e.g. VPS, logs, databases, restart). +* creating a discord bot and a private server +* setting up the project locally +* adding your own custom commands +* a technology overview # Download From 97bb393d8898c6b38edecf55931f0c025c230b61 Mon Sep 17 00:00:00 2001 From: Click <54646452+mikerasch@users.noreply.github.com> Date: Fri, 26 Aug 2022 03:07:45 -0500 Subject: [PATCH 11/18] Auto-close help thread if OP leaves server (#497) * Removal of threads when user leaves * Pass all the tests * Added purging of thread channels in DB after a week * Added some comments * Changed from deleting thread to archiving it * Changed naming of variables * Fixed various changes suggested by Zabuzard/Tais * Fixed various changes suggested by Zabuzard * Added annotation and changed variable name Co-authored-by: mikerasch --- PP.md | 5 +- .../togetherjava/tjbot/commands/Features.java | 4 +- .../tjbot/commands/help/AskCommand.java | 2 +- .../tjbot/commands/help/HelpSystemHelper.java | 19 +++++- .../help/HelpThreadMetadataPurger.java | 43 +++++++++++++ .../commands/help/ImplicitAskListener.java | 1 + .../help/OnGuildLeaveCloseThreadListener.java | 62 +++++++++++++++++++ .../db/V11__Add_Help_Thread_Metadata.sql | 6 ++ 8 files changed, 138 insertions(+), 4 deletions(-) create mode 100644 application/src/main/java/org/togetherjava/tjbot/commands/help/HelpThreadMetadataPurger.java create mode 100644 application/src/main/java/org/togetherjava/tjbot/commands/help/OnGuildLeaveCloseThreadListener.java create mode 100644 application/src/main/resources/db/V11__Add_Help_Thread_Metadata.sql diff --git a/PP.md b/PP.md index a82deaac35..ae5cce3537 100644 --- a/PP.md +++ b/PP.md @@ -36,7 +36,7 @@ In certain circumstances, you have the following data protection rights: ## Usage of Data -**TJ-Bot** may use stored data, as defined below, to offer different features and services. No usage of data outside of the aformentioned cases will happen and the data is not shared with any third-party site or service. +**TJ-Bot** may use stored data, as defined below, to offer different features and services. No usage of data outside of the aforementioned cases will happen and the data is not shared with any third-party site or service. ### Databases @@ -55,8 +55,11 @@ For example, **TJ-Bot** may associate your `user_id` with a `message_id` and a ` **TJ-Bot** may further store data that you explicitly provided for **TJ-Bot** to offer its services. For example the reason of a moderative action when using its moderation commands. +Furthermore, upon utilization of our help service, `user_id`s and `channel_id`s are stored to track when/how many questions a user asks. The data may be stored for up to **30** days. + The stored data is not linked to any information that is personally identifiable. + No other personal information outside of the above mentioned one will be stored. In particular, **TJ-Bot** does not store the content of sent messages. ### Log Files diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/Features.java b/application/src/main/java/org/togetherjava/tjbot/commands/Features.java index 0ce308e748..1ea24f590b 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/Features.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/Features.java @@ -61,7 +61,7 @@ private Features() { ModerationActionsStore actionsStore = new ModerationActionsStore(database); ModAuditLogWriter modAuditLogWriter = new ModAuditLogWriter(config); ScamHistoryStore scamHistoryStore = new ScamHistoryStore(database); - HelpSystemHelper helpSystemHelper = new HelpSystemHelper(config); + HelpSystemHelper helpSystemHelper = new HelpSystemHelper(config, database); // NOTE The system can add special system relevant commands also by itself, // hence this list may not necessarily represent the full list of all commands actually @@ -75,6 +75,7 @@ private Features() { features.add(new RemindRoutine(database)); features.add(new ScamHistoryPurgeRoutine(scamHistoryStore)); features.add(new BotMessageCleanup(config)); + features.add(new HelpThreadMetadataPurger(database)); features.add(new HelpThreadActivityUpdater(helpSystemHelper)); features .add(new AutoPruneHelperRoutine(config, helpSystemHelper, modAuditLogWriter, database)); @@ -88,6 +89,7 @@ private Features() { // Event receivers features.add(new RejoinModerationRoleListener(actionsStore, config)); + features.add(new OnGuildLeaveCloseThreadListener(database)); // Slash commands features.add(new LogLevelCommand()); diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/help/AskCommand.java b/application/src/main/java/org/togetherjava/tjbot/commands/help/AskCommand.java index c0acedb06c..7e30e86b09 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/help/AskCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/help/AskCommand.java @@ -46,7 +46,6 @@ public final class AskCommand extends SlashCommandAdapter { private static final String TITLE_OPTION = "title"; private static final String CATEGORY_OPTION = "category"; - private final HelpSystemHelper helper; /** @@ -120,6 +119,7 @@ private boolean handleIsValidTitle(@NotNull CharSequence title, @NotNull IReplyC private @NotNull RestAction handleEvent(@NotNull InteractionHook eventHook, @NotNull ThreadChannel threadChannel, @NotNull Member author, @NotNull String title, @NotNull String category, @NotNull Guild guild) { + helper.writeHelpThreadToDatabase(author, threadChannel); return sendInitialMessage(guild, threadChannel, author, title, category) .flatMap(any -> notifyUser(eventHook, threadChannel)) .flatMap(any -> helper.sendExplanationMessage(threadChannel)); diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/help/HelpSystemHelper.java b/application/src/main/java/org/togetherjava/tjbot/commands/help/HelpSystemHelper.java index f5dfa9733b..083a93c228 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/help/HelpSystemHelper.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/help/HelpSystemHelper.java @@ -11,6 +11,9 @@ import org.slf4j.LoggerFactory; import org.togetherjava.tjbot.config.Config; import org.togetherjava.tjbot.config.HelpSystemConfig; +import org.togetherjava.tjbot.db.Database; +import org.togetherjava.tjbot.db.generated.tables.HelpThreads; +import org.togetherjava.tjbot.db.generated.tables.records.HelpThreadsRecord; import java.awt.Color; import java.io.InputStream; @@ -47,14 +50,16 @@ public final class HelpSystemHelper { private final Predicate isStagingChannelName; private final String stagingChannelPattern; private final String categoryRoleSuffix; + private final Database database; /** * Creates a new instance. * * @param config the config to use */ - public HelpSystemHelper(@NotNull Config config) { + public HelpSystemHelper(@NotNull Config config, @NotNull Database database) { HelpSystemConfig helpConfig = config.getHelpSystem(); + this.database = database; overviewChannelPattern = helpConfig.getOverviewChannelPattern(); isOverviewChannelName = Pattern.compile(overviewChannelPattern).asMatchPredicate(); @@ -99,6 +104,18 @@ RestAction sendExplanationMessage(@NotNull MessageChannel threadChannel return action.setEmbeds(embeds); } + public void writeHelpThreadToDatabase(Member author, ThreadChannel threadChannel) { + database.write(content -> { + HelpThreadsRecord helpThreadsRecord = content.newRecord(HelpThreads.HELP_THREADS) + .setAuthorId(author.getIdLong()) + .setChannelId(threadChannel.getIdLong()) + .setCreatedAt(threadChannel.getTimeCreated().toInstant()); + if (helpThreadsRecord.update() == 0) { + helpThreadsRecord.insert(); + } + }); + } + private static @NotNull MessageEmbed embedWith(@NotNull CharSequence message) { return embedWith(message, null); } diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/help/HelpThreadMetadataPurger.java b/application/src/main/java/org/togetherjava/tjbot/commands/help/HelpThreadMetadataPurger.java new file mode 100644 index 0000000000..94333bf044 --- /dev/null +++ b/application/src/main/java/org/togetherjava/tjbot/commands/help/HelpThreadMetadataPurger.java @@ -0,0 +1,43 @@ +package org.togetherjava.tjbot.commands.help; + +import net.dv8tion.jda.api.JDA; +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.togetherjava.tjbot.commands.Routine; +import org.togetherjava.tjbot.db.Database; +import org.togetherjava.tjbot.db.generated.tables.HelpThreads; +import java.time.Instant; +import java.time.Period; +import java.util.concurrent.TimeUnit; + +/** + * Purge Routine to get rid of old thread creations in the database. + */ +public class HelpThreadMetadataPurger implements Routine { + private final Database database; + private static final Logger logger = LoggerFactory.getLogger(HelpThreadMetadataPurger.class); + private static final Period DELETE_MESSAGE_RECORDS_AFTER = Period.ofDays(30); + + public HelpThreadMetadataPurger(@NotNull Database database) { + this.database = database; + } + + @Override + public @NotNull Schedule createSchedule() { + return new Schedule(ScheduleMode.FIXED_RATE, 0, 4, TimeUnit.HOURS); + } + + @Override + public void runRoutine(@NotNull JDA jda) { + int recordsDeleted = + database.writeAndProvide(content -> content.deleteFrom(HelpThreads.HELP_THREADS)) + .where(HelpThreads.HELP_THREADS.CREATED_AT + .lessOrEqual(Instant.now().minus(DELETE_MESSAGE_RECORDS_AFTER))) + .execute(); + if (recordsDeleted > 0) { + logger.debug("{} old thread channels deleted because they are older than {}.", + recordsDeleted, DELETE_MESSAGE_RECORDS_AFTER); + } + } +} diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/help/ImplicitAskListener.java b/application/src/main/java/org/togetherjava/tjbot/commands/help/ImplicitAskListener.java index 3652ef0a4a..22a241fbe3 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/help/ImplicitAskListener.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/help/ImplicitAskListener.java @@ -157,6 +157,7 @@ private Optional getLastHelpThreadIfOnCooldown(long userId) { private @NotNull RestAction handleEvent(@NotNull ThreadChannel threadChannel, @NotNull Message message, @NotNull String title) { Member author = message.getMember(); + helper.writeHelpThreadToDatabase(author, threadChannel); userIdToLastHelpThread.put(author.getIdLong(), new HelpThread(threadChannel.getIdLong(), author.getIdLong(), Instant.now())); diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/help/OnGuildLeaveCloseThreadListener.java b/application/src/main/java/org/togetherjava/tjbot/commands/help/OnGuildLeaveCloseThreadListener.java new file mode 100644 index 0000000000..0fa1e72a74 --- /dev/null +++ b/application/src/main/java/org/togetherjava/tjbot/commands/help/OnGuildLeaveCloseThreadListener.java @@ -0,0 +1,62 @@ +package org.togetherjava.tjbot.commands.help; + +import net.dv8tion.jda.api.EmbedBuilder; +import net.dv8tion.jda.api.entities.MessageEmbed; +import net.dv8tion.jda.api.entities.ThreadChannel; +import net.dv8tion.jda.api.events.guild.member.GuildMemberRemoveEvent; +import net.dv8tion.jda.api.hooks.ListenerAdapter; +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.togetherjava.tjbot.commands.EventReceiver; +import org.togetherjava.tjbot.db.Database; +import org.togetherjava.tjbot.db.generated.tables.HelpThreads; + +import javax.annotation.Nonnull; +import java.util.*; + +/** + * Remove all thread channels associated to a user when they leave the guild. + */ +public class OnGuildLeaveCloseThreadListener extends ListenerAdapter implements EventReceiver { + private static final Logger logger = + LoggerFactory.getLogger(OnGuildLeaveCloseThreadListener.class); + private final Database database; + + public OnGuildLeaveCloseThreadListener(@NotNull Database database) { + this.database = database; + } + + @Override + public void onGuildMemberRemove(@Nonnull GuildMemberRemoveEvent leaveEvent) { + Set channelIds = getThreadsCreatedByLeaver(leaveEvent.getUser().getIdLong()); + for (long channelId : channelIds) { + closeThread(channelId, leaveEvent); + } + } + + public Set getThreadsCreatedByLeaver(long leaverId) { + return new HashSet<>(database + .readTransaction(context -> context.select(HelpThreads.HELP_THREADS.CHANNEL_ID)) + .from(HelpThreads.HELP_THREADS) + .where(HelpThreads.HELP_THREADS.AUTHOR_ID.eq(leaverId)) + .fetch(databaseMapper -> databaseMapper.getValue(HelpThreads.HELP_THREADS.CHANNEL_ID))); + } + + public void closeThread(long channelId, @NotNull GuildMemberRemoveEvent leaveEvent) { + ThreadChannel threadChannel = leaveEvent.getGuild().getThreadChannelById(channelId); + if (threadChannel == null) { + logger.warn( + "Attempted to archive thread id: '{}' but could not find thread in guild: '{}'.", + channelId, leaveEvent.getGuild().getName()); + return; + } + MessageEmbed embed = new EmbedBuilder().setTitle("OP left") + .setDescription("Closing thread...") + .setColor(HelpSystemHelper.AMBIENT_COLOR) + .build(); + threadChannel.sendMessageEmbeds(embed) + .flatMap(any -> threadChannel.getManager().setArchived(true)) + .queue(); + } +} diff --git a/application/src/main/resources/db/V11__Add_Help_Thread_Metadata.sql b/application/src/main/resources/db/V11__Add_Help_Thread_Metadata.sql new file mode 100644 index 0000000000..bb89e5a607 --- /dev/null +++ b/application/src/main/resources/db/V11__Add_Help_Thread_Metadata.sql @@ -0,0 +1,6 @@ +CREATE TABLE help_threads +( + channel_id BIGINT NOT NULL PRIMARY KEY, + author_id BIGINT NOT NULL, + created_at TIMESTAMP NOT NULL +) \ No newline at end of file From 38ef4954a5882ecb2379ed79da9d1f777b54d940 Mon Sep 17 00:00:00 2001 From: Click <54646452+mikerasch@users.noreply.github.com> Date: Fri, 26 Aug 2022 19:56:52 -0500 Subject: [PATCH 12/18] Fix sonar issues (#538) * Added documentation and private modifiers * Added more clarity to ensure optimal agreement on the use cases of parameters. * Fixed period at the end of the parameter documentation --- .../tjbot/commands/help/HelpSystemHelper.java | 3 ++- .../tjbot/commands/help/HelpThreadMetadataPurger.java | 5 +++++ .../commands/help/OnGuildLeaveCloseThreadListener.java | 9 +++++++-- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/help/HelpSystemHelper.java b/application/src/main/java/org/togetherjava/tjbot/commands/help/HelpSystemHelper.java index 083a93c228..c99cc119e4 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/help/HelpSystemHelper.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/help/HelpSystemHelper.java @@ -56,6 +56,7 @@ public final class HelpSystemHelper { * Creates a new instance. * * @param config the config to use + * @param database the database to store help thread metadata in */ public HelpSystemHelper(@NotNull Config config, @NotNull Database database) { HelpSystemConfig helpConfig = config.getHelpSystem(); @@ -104,7 +105,7 @@ RestAction sendExplanationMessage(@NotNull MessageChannel threadChannel return action.setEmbeds(embeds); } - public void writeHelpThreadToDatabase(Member author, ThreadChannel threadChannel) { + void writeHelpThreadToDatabase(Member author, ThreadChannel threadChannel) { database.write(content -> { HelpThreadsRecord helpThreadsRecord = content.newRecord(HelpThreads.HELP_THREADS) .setAuthorId(author.getIdLong()) diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/help/HelpThreadMetadataPurger.java b/application/src/main/java/org/togetherjava/tjbot/commands/help/HelpThreadMetadataPurger.java index 94333bf044..2fa502b76b 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/help/HelpThreadMetadataPurger.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/help/HelpThreadMetadataPurger.java @@ -19,6 +19,11 @@ public class HelpThreadMetadataPurger implements Routine { private static final Logger logger = LoggerFactory.getLogger(HelpThreadMetadataPurger.class); private static final Period DELETE_MESSAGE_RECORDS_AFTER = Period.ofDays(30); + /** + * Creates a new instance. + * + * @param database the database used to purge help thread metadata + */ public HelpThreadMetadataPurger(@NotNull Database database) { this.database = database; } diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/help/OnGuildLeaveCloseThreadListener.java b/application/src/main/java/org/togetherjava/tjbot/commands/help/OnGuildLeaveCloseThreadListener.java index 0fa1e72a74..4d8a945f5d 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/help/OnGuildLeaveCloseThreadListener.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/help/OnGuildLeaveCloseThreadListener.java @@ -23,6 +23,11 @@ public class OnGuildLeaveCloseThreadListener extends ListenerAdapter implements LoggerFactory.getLogger(OnGuildLeaveCloseThreadListener.class); private final Database database; + /** + * Creates a new instance. + * + * @param database database to use + */ public OnGuildLeaveCloseThreadListener(@NotNull Database database) { this.database = database; } @@ -35,7 +40,7 @@ public void onGuildMemberRemove(@Nonnull GuildMemberRemoveEvent leaveEvent) { } } - public Set getThreadsCreatedByLeaver(long leaverId) { + private Set getThreadsCreatedByLeaver(long leaverId) { return new HashSet<>(database .readTransaction(context -> context.select(HelpThreads.HELP_THREADS.CHANNEL_ID)) .from(HelpThreads.HELP_THREADS) @@ -43,7 +48,7 @@ public Set getThreadsCreatedByLeaver(long leaverId) { .fetch(databaseMapper -> databaseMapper.getValue(HelpThreads.HELP_THREADS.CHANNEL_ID))); } - public void closeThread(long channelId, @NotNull GuildMemberRemoveEvent leaveEvent) { + private void closeThread(long channelId, @NotNull GuildMemberRemoveEvent leaveEvent) { ThreadChannel threadChannel = leaveEvent.getGuild().getThreadChannelById(channelId); if (threadChannel == null) { logger.warn( From 9d46fde6b90a5bf2de43e98c0c2b8cfceb46b4d0 Mon Sep 17 00:00:00 2001 From: Mom0aut <30840092+Mom0aut@users.noreply.github.com> Date: Sun, 28 Aug 2022 10:00:19 +0200 Subject: [PATCH 13/18] Only allow media posts in media channels (#518) * added MemeListener checks if the sent message in Meme Channel contains Media * added MemeListenerTests * added Review Feedback * CodeFactor does not allow escaped Unicode * fixed typo * aded Review Feedback * added Review Feedback * added Review Feedback * added Review Feedback * added Review Feedback * added Review Feedback * added Review Feedback * added Review Feedback * added Review Feedback * Improved the unit tests * Use raw message when forwarding Co-authored-by: Zabuzard --- application/config.json.template | 3 +- .../togetherjava/tjbot/commands/Features.java | 7 +- .../mediaonly/MediaOnlyChannelListener.java | 79 +++++++++++++++ .../org/togetherjava/tjbot/config/Config.java | 15 ++- .../MediaOnlyChannelListenerTest.java | 96 +++++++++++++++++++ .../org/togetherjava/tjbot/jda/JdaTester.java | 95 +++++++++++++++--- 6 files changed, 277 insertions(+), 18 deletions(-) create mode 100644 application/src/main/java/org/togetherjava/tjbot/commands/mediaonly/MediaOnlyChannelListener.java create mode 100644 application/src/test/java/org/togetherjava/tjbot/commands/mediaonly/MediaOnlyChannelListenerTest.java diff --git a/application/config.json.template b/application/config.json.template index 6c433a2043..3a34d248fc 100644 --- a/application/config.json.template +++ b/application/config.json.template @@ -44,5 +44,6 @@ "Other" ], "categoryRoleSuffix": " - Helper" - } + }, + "mediaOnlyChannelPattern": "memes" } diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/Features.java b/application/src/main/java/org/togetherjava/tjbot/commands/Features.java index 1ea24f590b..357afc9d48 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/Features.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/Features.java @@ -2,11 +2,15 @@ import net.dv8tion.jda.api.JDA; import org.jetbrains.annotations.NotNull; -import org.togetherjava.tjbot.commands.basic.*; +import org.togetherjava.tjbot.commands.basic.PingCommand; +import org.togetherjava.tjbot.commands.basic.RoleSelectCommand; +import org.togetherjava.tjbot.commands.basic.SuggestionsUpDownVoter; +import org.togetherjava.tjbot.commands.basic.VcActivityCommand; import org.togetherjava.tjbot.commands.filesharing.FileSharingMessageListener; import org.togetherjava.tjbot.commands.help.*; import org.togetherjava.tjbot.commands.mathcommands.TeXCommand; import org.togetherjava.tjbot.commands.mathcommands.wolframalpha.WolframAlphaCommand; +import org.togetherjava.tjbot.commands.mediaonly.MediaOnlyChannelListener; import org.togetherjava.tjbot.commands.moderation.*; import org.togetherjava.tjbot.commands.moderation.scam.ScamBlocker; import org.togetherjava.tjbot.commands.moderation.scam.ScamHistoryPurgeRoutine; @@ -85,6 +89,7 @@ private Features() { features.add(new SuggestionsUpDownVoter(config)); features.add(new ScamBlocker(actionsStore, scamHistoryStore, config)); features.add(new ImplicitAskListener(config, helpSystemHelper)); + features.add(new MediaOnlyChannelListener(config)); features.add(new FileSharingMessageListener(config)); // Event receivers diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/mediaonly/MediaOnlyChannelListener.java b/application/src/main/java/org/togetherjava/tjbot/commands/mediaonly/MediaOnlyChannelListener.java new file mode 100644 index 0000000000..dd411f528a --- /dev/null +++ b/application/src/main/java/org/togetherjava/tjbot/commands/mediaonly/MediaOnlyChannelListener.java @@ -0,0 +1,79 @@ +package org.togetherjava.tjbot.commands.mediaonly; + +import net.dv8tion.jda.api.EmbedBuilder; +import net.dv8tion.jda.api.MessageBuilder; +import net.dv8tion.jda.api.entities.Message; +import net.dv8tion.jda.api.entities.MessageEmbed; +import net.dv8tion.jda.api.events.message.MessageReceivedEvent; +import net.dv8tion.jda.api.requests.RestAction; +import net.dv8tion.jda.api.requests.restaction.AuditableRestAction; +import org.togetherjava.tjbot.commands.MessageReceiverAdapter; +import org.togetherjava.tjbot.config.Config; + +import javax.annotation.Nonnull; +import java.awt.Color; +import java.util.regex.Pattern; + +/** + * Listener that receives all sent messages from the Media Only Channels, checks if the message has + * media attached. + *

    + * If there was no media attached, delete the message and send the user a DM, telling what they did + * wrong. + */ +public final class MediaOnlyChannelListener extends MessageReceiverAdapter { + + /** + * Creates a MediaOnlyChannelListener to receive all message sent in MediaOnly channel. + * + * @param config to find MediaOnly channels + */ + public MediaOnlyChannelListener(Config config) { + super(Pattern.compile(config.getMediaOnlyChannelPattern())); + } + + + @Override + public void onMessageReceived(MessageReceivedEvent event) { + if (event.getAuthor().isBot() || event.isWebhookMessage()) { + return; + } + + if (messageHasNoMediaAttached(event)) { + deleteMessage(event).flatMap(any -> dmUser(event)).queue(); + } + } + + private static boolean messageHasNoMediaAttached(MessageReceivedEvent event) { + Message message = event.getMessage(); + return message.getAttachments().isEmpty() && message.getEmbeds().isEmpty(); + } + + @Nonnull + private AuditableRestAction deleteMessage(MessageReceivedEvent event) { + return event.getMessage().delete(); + } + + @Nonnull + private RestAction dmUser(MessageReceivedEvent event) { + return dmUser(event.getMessage()); + } + + @Nonnull + private RestAction dmUser(Message originalMessage) { + String originalMessageContent = originalMessage.getContentRaw(); + MessageEmbed originalMessageEmbed = + new EmbedBuilder().setDescription(originalMessageContent) + .setColor(Color.ORANGE) + .build(); + + Message dmMessage = new MessageBuilder( + "Hey there, you posted a message without media (image, video, link) in a media-only channel. Please see the description of the channel for details and then repost with media attached, thanks 😀") + .setEmbeds(originalMessageEmbed) + .build(); + + return originalMessage.getAuthor() + .openPrivateChannel() + .flatMap(channel -> channel.sendMessage(dmMessage)); + } +} diff --git a/application/src/main/java/org/togetherjava/tjbot/config/Config.java b/application/src/main/java/org/togetherjava/tjbot/config/Config.java index 9888d12191..471508da96 100644 --- a/application/src/main/java/org/togetherjava/tjbot/config/Config.java +++ b/application/src/main/java/org/togetherjava/tjbot/config/Config.java @@ -29,6 +29,8 @@ public final class Config { private final String wolframAlphaAppId; private final HelpSystemConfig helpSystem; + private final String mediaOnlyChannelPattern; + @SuppressWarnings("ConstructorWithTooManyParameters") @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) private Config(@JsonProperty("token") String token, @@ -45,7 +47,8 @@ private Config(@JsonProperty("token") String token, @JsonProperty("quarantinedRolePattern") String quarantinedRolePattern, @JsonProperty("scamBlocker") ScamBlockerConfig scamBlocker, @JsonProperty("wolframAlphaAppId") String wolframAlphaAppId, - @JsonProperty("helpSystem") HelpSystemConfig helpSystem) { + @JsonProperty("helpSystem") HelpSystemConfig helpSystem, + @JsonProperty("mediaOnlyChannelPattern") String mediaOnlyChannelPattern) { this.token = token; this.gistApiKey = gistApiKey; this.databasePath = databasePath; @@ -61,6 +64,7 @@ private Config(@JsonProperty("token") String token, this.scamBlocker = scamBlocker; this.wolframAlphaAppId = wolframAlphaAppId; this.helpSystem = helpSystem; + this.mediaOnlyChannelPattern = mediaOnlyChannelPattern; } /** @@ -216,4 +220,13 @@ public String getQuarantinedRolePattern() { public @NotNull HelpSystemConfig getHelpSystem() { return helpSystem; } + + /** + * Gets the REGEX pattern used to identify the channel that is supposed to contain only Media. + * + * @return the channel name pattern + */ + public @NotNull String getMediaOnlyChannelPattern() { + return mediaOnlyChannelPattern; + } } diff --git a/application/src/test/java/org/togetherjava/tjbot/commands/mediaonly/MediaOnlyChannelListenerTest.java b/application/src/test/java/org/togetherjava/tjbot/commands/mediaonly/MediaOnlyChannelListenerTest.java new file mode 100644 index 0000000000..1d16b419f7 --- /dev/null +++ b/application/src/test/java/org/togetherjava/tjbot/commands/mediaonly/MediaOnlyChannelListenerTest.java @@ -0,0 +1,96 @@ +package org.togetherjava.tjbot.commands.mediaonly; + +import net.dv8tion.jda.api.EmbedBuilder; +import net.dv8tion.jda.api.MessageBuilder; +import net.dv8tion.jda.api.entities.Message; +import net.dv8tion.jda.api.entities.MessageEmbed; +import net.dv8tion.jda.api.events.message.MessageReceivedEvent; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.togetherjava.tjbot.config.Config; +import org.togetherjava.tjbot.jda.JdaTester; + +import javax.annotation.Nonnull; +import java.util.List; + +import static org.mockito.Mockito.*; + +final class MediaOnlyChannelListenerTest { + + private JdaTester jdaTester; + private MediaOnlyChannelListener mediaOnlyChannelListener; + + @BeforeEach + void setUp() { + jdaTester = new JdaTester(); + + Config config = mock(Config.class); + when(config.getMediaOnlyChannelPattern()).thenReturn("any"); + + mediaOnlyChannelListener = new MediaOnlyChannelListener(config); + } + + @Test + void deletesMessageWithoutMedia() { + // GIVEN a message without media + Message message = new MessageBuilder().setContent("any").build(); + + // WHEN sending the message + MessageReceivedEvent event = sendMessage(message); + + // THEN it gets deleted + verify(event.getMessage()).delete(); + } + + @Test + void keepsMessageWithEmbed() { + // GIVEN a message with an embed + MessageEmbed embed = new EmbedBuilder().setDescription("any").build(); + Message message = new MessageBuilder().setContent("any").setEmbeds(embed).build(); + + // WHEN sending the message + MessageReceivedEvent event = sendMessage(message); + + // THEN it does not get deleted + verify(event.getMessage(), never()).delete(); + } + + @Test + void keepsMessageWithAttachment() { + // GIVEN a message with an attachment + Message message = new MessageBuilder().setContent("any").build(); + List attachments = List.of(mock(Message.Attachment.class)); + + // WHEN sending the message + MessageReceivedEvent event = sendMessage(message, attachments); + + // THEN it does not get deleted + verify(event.getMessage(), never()).delete(); + + } + + @Test + void sendsAuthorDmUponDeletion() { + // GIVEN a message without media + Message message = new MessageBuilder().setContent("any").build(); + + // WHEN sending the message + MessageReceivedEvent event = sendMessage(message); + + // THEN the author receives a DM + verify(jdaTester.getPrivateChannelSpy()).sendMessage(any(Message.class)); + } + + @Nonnull + private MessageReceivedEvent sendMessage(Message message) { + return sendMessage(message, List.of()); + } + + @Nonnull + private MessageReceivedEvent sendMessage(Message message, + List attachments) { + MessageReceivedEvent event = jdaTester.createMessageReceiveEvent(message, attachments); + mediaOnlyChannelListener.onMessageReceived(event); + return event; + } +} diff --git a/application/src/test/java/org/togetherjava/tjbot/jda/JdaTester.java b/application/src/test/java/org/togetherjava/tjbot/jda/JdaTester.java index ab2428770e..1c4cf85100 100644 --- a/application/src/test/java/org/togetherjava/tjbot/jda/JdaTester.java +++ b/application/src/test/java/org/togetherjava/tjbot/jda/JdaTester.java @@ -6,6 +6,7 @@ import net.dv8tion.jda.api.entities.*; import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent; +import net.dv8tion.jda.api.events.message.MessageReceivedEvent; import net.dv8tion.jda.api.exceptions.ErrorResponseException; import net.dv8tion.jda.api.interactions.InteractionHook; import net.dv8tion.jda.api.interactions.callbacks.IReplyCallback; @@ -32,12 +33,14 @@ import org.togetherjava.tjbot.commands.SlashCommand; import org.togetherjava.tjbot.commands.componentids.ComponentIdGenerator; +import javax.annotation.Nonnull; import java.util.EnumSet; import java.util.List; import java.util.Set; import java.util.UUID; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.atomic.AtomicLong; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; @@ -80,7 +83,6 @@ public final class JdaTester { private static final long PRIVATE_CHANNEL_ID = 1; private static final long GUILD_ID = 1; private static final long TEXT_CHANNEL_ID = 1; - private final JDAImpl jda; private final MemberImpl member; private final GuildImpl guild; @@ -92,6 +94,7 @@ public final class JdaTester { private final PrivateChannelImpl privateChannel; private final InteractionHook interactionHook; private final ReplyCallbackAction replyCallbackAction; + private final AtomicLong responseNumber = new AtomicLong(0); /** * Creates a new instance. The instance uses a fresh and isolated mocked JDA setup. @@ -155,8 +158,7 @@ public JdaTester() { .thenReturn(replyAction); doNothing().when(replyAction).queue(); - auditableRestAction = (AuditableRestActionImpl) mock(AuditableRestActionImpl.class); - doNothing().when(auditableRestAction).queue(); + auditableRestAction = createSucceededActionMock(null, AuditableRestActionImpl.class); doNothing().when(webhookMessageUpdateAction).queue(); doReturn(webhookMessageUpdateAction).when(webhookMessageUpdateAction) @@ -166,7 +168,7 @@ public JdaTester() { doReturn(selfMember).when(guild).getMember(selfUser); doReturn(member).when(guild).getMember(not(eq(selfUser))); - RestAction userAction = createSucceededActionMock(member.getUser()); + RestAction userAction = createSucceededActionMock(member.getUser(), RestAction.class); when(jda.retrieveUserById(anyLong())).thenReturn(userAction); doReturn(null).when(textChannel).retrieveMessageById(any()); @@ -184,11 +186,14 @@ public JdaTester() { doNothing().when(messageAction).queue(); when(messageAction.content(any())).thenReturn(messageAction); - RestAction privateChannelAction = createSucceededActionMock(privateChannel); + RestAction privateChannelAction = + createSucceededActionMock(privateChannel, RestAction.class); when(jda.openPrivateChannelById(anyLong())).thenReturn(privateChannelAction); when(jda.openPrivateChannelById(anyString())).thenReturn(privateChannelAction); + doReturn(privateChannelAction).when(user).openPrivateChannel(); doReturn(null).when(privateChannel).retrieveMessageById(any()); doReturn(messageAction).when(privateChannel).sendMessage(anyString()); + doReturn(messageAction).when(privateChannel).sendMessage(any(Message.class)); doReturn(messageAction).when(privateChannel).sendMessageEmbeds(any(), any()); doReturn(messageAction).when(privateChannel).sendMessageEmbeds(any()); } @@ -375,19 +380,22 @@ public JdaTester() { * var jdaTester = new JdaTester(); * * var message = new MessageBuilder("Hello World!").build(); - * var action = jdaTester.createSucceededActionMock(message); + * var action = jdaTester.createSucceededActionMock(message, RestAction.class); * * doReturn(action).when(jdaTester.getTextChannelSpy()).retrieveMessageById("1"); * } * * * @param t the object to consume on success + * @param restActionType class token of the type of the Rest Action to return * @param the type of the object to consume + * @param the specific type of the Rest Action to return * @return the mocked action */ @SuppressWarnings("unchecked") - public @NotNull RestAction createSucceededActionMock(@Nullable T t) { - RestAction action = (RestAction) mock(RestAction.class); + public > @NotNull R createSucceededActionMock(@Nullable T t, + Class restActionType) { + R action = mock(restActionType); Answer successExecution = invocation -> { Consumer successConsumer = invocation.getArgument(0); @@ -397,7 +405,7 @@ public JdaTester() { Answer> mapExecution = invocation -> { Function mapFunction = invocation.getArgument(0); Object result = mapFunction.apply(t); - return createSucceededActionMock(result); + return createSucceededActionMock(result, RestAction.class); }; Answer> flatMapExecution = invocation -> { Function> flatMapFunction = invocation.getArgument(0); @@ -417,6 +425,20 @@ public JdaTester() { return action; } + /** + * Variant of {@link #createSucceededActionMock(Object, Class)} returning a plain + * {@link RestAction}. + * + * @param t the object to consume on success + * @param the type of the object to consume + * @return the mocked action + * @see #createSucceededActionMock(Object, Class) + */ + @SuppressWarnings("unchecked") + public @NotNull RestAction createSucceededActionMock(@Nullable T t) { + return createSucceededActionMock(t, RestAction.class); + } + /** * Creates a mocked action that always fails and consumes the given failure reason. *

    @@ -429,19 +451,22 @@ public JdaTester() { * var jdaTester = new JdaTester(); * * var reason = new FooException(); - * var action = jdaTester.createFailedActionMock(reason); + * var action = jdaTester.createFailedActionMock(reason, RestAction.class); * * doReturn(action).when(jdaTester.getTextChannelSpy()).retrieveMessageById("1"); * } * * * @param failureReason the reason to consume on failure + * @param restActionType class token of the type of the Rest Action to return * @param the type of the object the action would contain if it would succeed + * @param the specific type of the Rest Action to return * @return the mocked action */ @SuppressWarnings("unchecked") - public @NotNull RestAction createFailedActionMock(@NotNull Throwable failureReason) { - RestAction action = (RestAction) mock(RestAction.class); + public > @NotNull R createFailedActionMock( + @NotNull Throwable failureReason, Class restActionType) { + R action = mock(restActionType); Answer failureExecution = invocation -> { Consumer failureConsumer = invocation.getArgument(1); @@ -452,12 +477,13 @@ public JdaTester() { Answer> errorMapExecution = invocation -> { Function mapFunction = invocation.getArgument(0); Object result = mapFunction.apply(failureReason); - return createSucceededActionMock(result); + return createSucceededActionMock(result, RestAction.class); }; - Answer> mapExecution = invocation -> createFailedActionMock(failureReason); + Answer> mapExecution = + invocation -> createFailedActionMock(failureReason, RestAction.class); Answer> flatMapExecution = - invocation -> createFailedActionMock(failureReason); + invocation -> createFailedActionMock(failureReason, RestAction.class); doNothing().when(action).queue(); doNothing().when(action).queue(any()); @@ -473,6 +499,20 @@ public JdaTester() { return action; } + /** + * Variant of {@link #createFailedActionMock(Throwable, Class)} returning a plain + * {@link RestAction}. + * + * @param failureReason the reason to consume on failure + * @param the type of the object the action would contain if it would succeed + * @return the mocked action + * @see #createFailedActionMock(Throwable, Class) + */ + @SuppressWarnings("unchecked") + public @NotNull RestAction createFailedActionMock(@NotNull Throwable failureReason) { + return createFailedActionMock(failureReason, RestAction.class); + } + /** * Creates an exception used by JDA on failure in most calls to the Discord API. *

    @@ -487,6 +527,24 @@ public JdaTester() { return ErrorResponseException.create(reason, new Response(null, -1, "", -1, Set.of())); } + /** + * Creates a Mockito mocked message receive event, which can be used for + * {@link org.togetherjava.tjbot.commands.MessageReceiver#onMessageReceived(MessageReceivedEvent)}. + * + * @param message the message that has been received + * @param attachments attachments of the message, empty if none + * @return the event of receiving the given message + */ + @Nonnull + public MessageReceivedEvent createMessageReceiveEvent(Message message, + List attachments) { + Message spyMessage = spy(message); + mockMessage(spyMessage); + doReturn(attachments).when(spyMessage).getAttachments(); + + return new MessageReceivedEvent(jda, responseNumber.getAndIncrement(), spyMessage); + } + private void mockInteraction(@NotNull IReplyCallback interaction) { doReturn(replyAction).when(interaction).reply(anyString()); doReturn(replyAction).when(interaction).replyEmbeds(ArgumentMatchers.any()); @@ -524,5 +582,12 @@ private void mockMessage(@NotNull Message message) { doReturn(member).when(message).getMember(); doReturn(member.getUser()).when(message).getAuthor(); + + doReturn(textChannel).when(message).getChannel(); + doReturn(1L).when(message).getIdLong(); + doReturn(false).when(message).isWebhookMessage(); + + doReturn(message.getContentRaw()).when(message).getContentDisplay(); + doReturn(message.getContentRaw()).when(message).getContentStripped(); } } From dbdbd408cd710023e5e58d62efb6351961f58a5f Mon Sep 17 00:00:00 2001 From: Daniel Tischner Date: Sun, 28 Aug 2022 10:00:49 +0200 Subject: [PATCH 14/18] Fix bug with /audit unable to retrieve users (#527) * Fixed blocking bugs and improved readability of page-turn mechanism * Display last page by default, max page size 10 --- .../commands/moderation/AuditCommand.java | 216 ++++++++++-------- 1 file changed, 124 insertions(+), 92 deletions(-) diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/AuditCommand.java b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/AuditCommand.java index 0ff92a1a0b..2774271f5f 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/AuditCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/AuditCommand.java @@ -11,18 +11,17 @@ import net.dv8tion.jda.api.interactions.commands.OptionType; import net.dv8tion.jda.api.interactions.components.ActionRow; import net.dv8tion.jda.api.interactions.components.buttons.Button; +import net.dv8tion.jda.api.requests.RestAction; import net.dv8tion.jda.api.utils.TimeUtil; - +import net.dv8tion.jda.internal.requests.CompletedRestAction; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; - import org.togetherjava.tjbot.commands.SlashCommandAdapter; import org.togetherjava.tjbot.commands.SlashCommandVisibility; import java.time.Instant; import java.time.ZoneOffset; import java.util.*; -import java.util.function.Function; import java.util.stream.Collectors; /** @@ -36,14 +35,14 @@ public final class AuditCommand extends SlashCommandAdapter { private static final String TARGET_OPTION = "user"; private static final String COMMAND_NAME = "audit"; private static final String ACTION_VERB = "audit"; - private static final int MAX_PAGE_LENGTH = 25; + private static final int MAX_PAGE_LENGTH = 10; private static final String PREVIOUS_BUTTON_LABEL = "⬅"; private static final String NEXT_BUTTON_LABEL = "➡"; private final ModerationActionsStore actionsStore; /** * Constructs an instance. - * + * * @param actionsStore used to store actions issued by this command */ public AuditCommand(@NotNull ModerationActionsStore actionsStore) { @@ -56,63 +55,6 @@ public AuditCommand(@NotNull ModerationActionsStore actionsStore) { this.actionsStore = Objects.requireNonNull(actionsStore); } - private static @NotNull EmbedBuilder createSummaryEmbed(@NotNull User user, - @NotNull Collection actions) { - return new EmbedBuilder().setTitle("Audit log of **%s**".formatted(user.getAsTag())) - .setAuthor(user.getName(), null, user.getAvatarUrl()) - .setDescription(createSummaryMessageDescription(actions)) - .setColor(ModerationUtils.AMBIENT_COLOR); - } - - private static @NotNull String createSummaryMessageDescription( - @NotNull Collection actions) { - int actionAmount = actions.size(); - - String shortSummary = "There are **%s actions** against the user." - .formatted(actionAmount == 0 ? "no" : actionAmount); - - if (actionAmount == 0) { - return shortSummary; - } - - // Summary of all actions with their count, like "- Warn: 5", descending - Map actionTypeToCount = actions.stream() - .collect(Collectors.groupingBy(ActionRecord::actionType, Collectors.counting())); - - String typeCountSummary = actionTypeToCount.entrySet() - .stream() - .filter(typeAndCount -> typeAndCount.getValue() > 0) - .sorted(Map.Entry.comparingByValue().reversed()) - .map(typeAndCount -> "- **%s**: %d".formatted(typeAndCount.getKey(), - typeAndCount.getValue())) - .collect(Collectors.joining("\n")); - - return shortSummary + "\n" + typeCountSummary; - } - - private static @NotNull MessageEmbed.Field actionToField(@NotNull ActionRecord action, - @NotNull JDA jda) { - Function formatTime = instant -> { - if (instant == null) { - return ""; - } - return TimeUtil.getDateTimeString(instant.atOffset(ZoneOffset.UTC)); - }; - - User author = jda.getUserById(action.authorId()); - - Instant expiresAt = action.actionExpiresAt(); - String expiresAtFormatted = expiresAt == null ? "" - : "\nTemporary action, expires at: " + formatTime.apply(expiresAt); - - return new MessageEmbed.Field( - action.actionType().name() + " by " - + (author == null ? "(unknown user)" : author.getAsTag()), - action.reason() + "\nIssued at: " + formatTime.apply(action.issuedAt()) - + expiresAtFormatted, - false); - } - @Override public void onSlashCommand(@NotNull SlashCommandInteractionEvent event) { OptionMapping targetOption = @@ -127,10 +69,8 @@ public void onSlashCommand(@NotNull SlashCommandInteractionEvent event) { return; } - event - .reply(auditUser(guild.getIdLong(), target.getIdLong(), event.getMember().getIdLong(), - 1, event.getJDA())) - .queue(); + auditUser(guild.getIdLong(), target.getIdLong(), event.getMember().getIdLong(), -1, + event.getJDA()).flatMap(event::reply).queue(); } @SuppressWarnings("BooleanMethodNameMustStartWithQuestion") @@ -143,6 +83,32 @@ private boolean handleChecks(@NotNull Member bot, @NotNull Member author, return ModerationUtils.handleCanInteractWithTarget(ACTION_VERB, bot, author, target, event); } + /** + * @param pageNumber page number to display when actions are divided into pages and each page + * can contain {@link AuditCommand#MAX_PAGE_LENGTH} actions, {@code -1} encodes the last + * page + */ + private @NotNull RestAction auditUser(long guildId, long targetId, long callerId, + int pageNumber, @NotNull JDA jda) { + List actions = actionsStore.getActionsByTargetAscending(guildId, targetId); + List> groupedActions = groupActionsByPages(actions); + int totalPages = groupedActions.size(); + + int pageNumberInLimits; + if (pageNumber == -1) { + pageNumberInLimits = totalPages; + } else { + pageNumberInLimits = clamp(1, pageNumber, totalPages); + } + + return jda.retrieveUserById(targetId) + .map(user -> createSummaryEmbed(user, actions)) + .flatMap(auditEmbed -> attachEmbedFields(auditEmbed, groupedActions, pageNumberInLimits, + totalPages, jda)) + .map(auditEmbed -> attachPageTurnButtons(auditEmbed, pageNumberInLimits, totalPages, + guildId, targetId, callerId)); + } + private @NotNull List> groupActionsByPages( @NotNull List actions) { List> groupedActions = new ArrayList<>(); @@ -157,47 +123,112 @@ private boolean handleChecks(@NotNull Member bot, @NotNull Member author, return groupedActions; } - /** - * @param pageNumber page number to display when actions are divided into pages and each page - * can contain {@link AuditCommand#MAX_PAGE_LENGTH} actions - */ - private @NotNull Message auditUser(long guildId, long targetId, long callerId, int pageNumber, - @NotNull JDA jda) { - List actions = actionsStore.getActionsByTargetAscending(guildId, targetId); - List> groupedActions = groupActionsByPages(actions); - int totalPages = groupedActions.size(); + private static int clamp(int minInclusive, int value, int maxInclusive) { + return Math.min(Math.max(minInclusive, value), maxInclusive); + } + + private static @NotNull EmbedBuilder createSummaryEmbed(@NotNull User user, + @NotNull Collection actions) { + return new EmbedBuilder().setTitle("Audit log of **%s**".formatted(user.getAsTag())) + .setAuthor(user.getName(), null, user.getAvatarUrl()) + .setDescription(createSummaryMessageDescription(actions)) + .setColor(ModerationUtils.AMBIENT_COLOR); + } - // Handles the case of too low page number and too high page number - pageNumber = Math.max(1, pageNumber); - pageNumber = Math.min(totalPages, pageNumber); + private static @NotNull String createSummaryMessageDescription( + @NotNull Collection actions) { + int actionAmount = actions.size(); - EmbedBuilder audit = createSummaryEmbed(jda.retrieveUserById(targetId).complete(), actions); + String shortSummary = "There are **%s actions** against the user." + .formatted(actionAmount == 0 ? "no" : actionAmount); + + if (actionAmount == 0) { + return shortSummary; + } + // Summary of all actions with their count, like "- Warn: 5", descending + Map actionTypeToCount = actions.stream() + .collect(Collectors.groupingBy(ActionRecord::actionType, Collectors.counting())); + + String typeCountSummary = actionTypeToCount.entrySet() + .stream() + .filter(typeAndCount -> typeAndCount.getValue() > 0) + .sorted(Map.Entry.comparingByValue().reversed()) + .map(typeAndCount -> "- **%s**: %d".formatted(typeAndCount.getKey(), + typeAndCount.getValue())) + .collect(Collectors.joining("\n")); + + return shortSummary + "\n" + typeCountSummary; + } + + private @NotNull RestAction attachEmbedFields(@NotNull EmbedBuilder auditEmbed, + @NotNull List> groupedActions, int pageNumber, + int totalPages, @NotNull JDA jda) { if (groupedActions.isEmpty()) { - return new MessageBuilder(audit.build()).build(); + return new CompletedRestAction<>(jda, auditEmbed); } + List> embedFieldTasks = new ArrayList<>(); groupedActions.get(pageNumber - 1) - .forEach(action -> audit.addField(actionToField(action, jda))); + .forEach(action -> embedFieldTasks.add(actionToField(action, jda))); + + return RestAction.allOf(embedFieldTasks).map(embedFields -> { + embedFields.forEach(auditEmbed::addField); + + auditEmbed.setFooter("Page: " + pageNumber + "/" + totalPages); + return auditEmbed; + }); + } + + private static @NotNull RestAction actionToField( + @NotNull ActionRecord action, @NotNull JDA jda) { + return jda.retrieveUserById(action.authorId()) + .map(author -> author == null ? "(unknown user)" : author.getAsTag()) + .map(authorText -> { + String expiresAtFormatted = action.actionExpiresAt() == null ? "" + : "\nTemporary action, expires at: " + formatTime(action.actionExpiresAt()); + + String fieldName = "%s by %s".formatted(action.actionType().name(), authorText); + String fieldDescription = """ + %s + Issued at: %s%s + """.formatted(action.reason(), formatTime(action.issuedAt()), + expiresAtFormatted); + + return new MessageEmbed.Field(fieldName, fieldDescription, false); + }); + } + + private static @NotNull String formatTime(@NotNull Instant when) { + return TimeUtil.getDateTimeString(when.atOffset(ZoneOffset.UTC)); + } + + private @NotNull Message attachPageTurnButtons(@NotNull EmbedBuilder auditEmbed, int pageNumber, + int totalPages, long guildId, long targetId, long callerId) { + var messageBuilder = new MessageBuilder(auditEmbed.build()); + + if (totalPages <= 1) { + return messageBuilder.build(); + } + ActionRow pageTurnButtons = + createPageTurnButtons(guildId, targetId, callerId, pageNumber, totalPages); - return new MessageBuilder(audit.setFooter("Page: " + pageNumber + "/" + totalPages).build()) - .setActionRows(makeActionRow(guildId, targetId, callerId, pageNumber, totalPages)) - .build(); + return messageBuilder.setActionRows(pageTurnButtons).build(); } - private @NotNull ActionRow makeActionRow(long guildId, long targetId, long callerId, + private @NotNull ActionRow createPageTurnButtons(long guildId, long targetId, long callerId, int pageNumber, int totalPages) { int previousButtonTurnPageBy = -1; Button previousButton = createPageTurnButton(PREVIOUS_BUTTON_LABEL, guildId, targetId, callerId, pageNumber, previousButtonTurnPageBy); - if (pageNumber == 1) { + if (pageNumber <= 1) { previousButton = previousButton.asDisabled(); } int nextButtonTurnPageBy = 1; Button nextButton = createPageTurnButton(NEXT_BUTTON_LABEL, guildId, targetId, callerId, pageNumber, nextButtonTurnPageBy); - if (pageNumber == totalPages) { + if (pageNumber >= totalPages) { nextButton = nextButton.asDisabled(); } @@ -213,11 +244,11 @@ private boolean handleChecks(@NotNull Member bot, @NotNull Member author, @Override public void onButtonClick(@NotNull ButtonInteractionEvent event, @NotNull List args) { - long callerId = Long.parseLong(args.get(2)); - long interactorId = event.getMember().getIdLong(); + long commandUserId = Long.parseLong(args.get(2)); + long buttonUserId = event.getMember().getIdLong(); - if (callerId != interactorId) { - event.reply("Only the user who triggered the command can use these buttons.") + if (commandUserId != buttonUserId) { + event.reply("Only the user who triggered the command can turn pages.") .setEphemeral(true) .queue(); @@ -231,7 +262,8 @@ public void onButtonClick(@NotNull ButtonInteractionEvent event, @NotNull List Date: Sun, 28 Aug 2022 10:01:05 +0200 Subject: [PATCH 15/18] Disallow 'help' in title of help threads (#537) --- .../org/togetherjava/tjbot/commands/help/AskCommand.java | 9 ++++++--- .../tjbot/commands/help/HelpSystemHelper.java | 4 +++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/help/AskCommand.java b/application/src/main/java/org/togetherjava/tjbot/commands/help/AskCommand.java index 7e30e86b09..70160c207b 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/help/AskCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/help/AskCommand.java @@ -107,9 +107,12 @@ private boolean handleIsValidTitle(@NotNull CharSequence title, @NotNull IReplyC return true; } - event.reply( - "Sorry, but the title length (after removal of special characters) has to be between %d and %d." - .formatted(TITLE_COMPACT_LENGTH_MIN, TITLE_COMPACT_LENGTH_MAX)) + event.reply(""" + Sorry, but your title is invalid. Please pick a title where: + • length is between %d and %d + • must not contain the word 'help' + Thanks, and sorry for the inconvenience 👍 + """.formatted(TITLE_COMPACT_LENGTH_MIN, TITLE_COMPACT_LENGTH_MAX)) .setEphemeral(true) .queue(); diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/help/HelpSystemHelper.java b/application/src/main/java/org/togetherjava/tjbot/commands/help/HelpSystemHelper.java index c99cc119e4..2d2e49114c 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/help/HelpSystemHelper.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/help/HelpSystemHelper.java @@ -18,6 +18,7 @@ import java.awt.Color; import java.io.InputStream; import java.util.List; +import java.util.Locale; import java.util.Optional; import java.util.function.Predicate; import java.util.regex.Matcher; @@ -208,7 +209,8 @@ static boolean isTitleValid(@NotNull CharSequence title) { String titleCompact = TITLE_COMPACT_REMOVAL_PATTERN.matcher(title).replaceAll(""); return titleCompact.length() >= TITLE_COMPACT_LENGTH_MIN - && titleCompact.length() <= TITLE_COMPACT_LENGTH_MAX; + && titleCompact.length() <= TITLE_COMPACT_LENGTH_MAX + && !titleCompact.toLowerCase(Locale.US).contains("help"); } @NotNull From b5b7c594cf65506af384583947fb81fcc5a76832 Mon Sep 17 00:00:00 2001 From: Daniel Tischner Date: Sun, 28 Aug 2022 10:04:02 +0200 Subject: [PATCH 16/18] Adding help thread auto archiver (#540) * added "handleRequireOverviewChannel" helper to reduce code duplication --- .../togetherjava/tjbot/commands/Features.java | 1 + .../tjbot/commands/help/HelpSystemHelper.java | 26 ++++- .../help/HelpThreadActivityUpdater.java | 32 ++---- .../commands/help/HelpThreadAutoArchiver.java | 100 ++++++++++++++++++ .../help/HelpThreadOverviewUpdater.java | 35 ++---- 5 files changed, 135 insertions(+), 59 deletions(-) create mode 100644 application/src/main/java/org/togetherjava/tjbot/commands/help/HelpThreadAutoArchiver.java diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/Features.java b/application/src/main/java/org/togetherjava/tjbot/commands/Features.java index 357afc9d48..4a1f831fe2 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/Features.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/Features.java @@ -83,6 +83,7 @@ private Features() { features.add(new HelpThreadActivityUpdater(helpSystemHelper)); features .add(new AutoPruneHelperRoutine(config, helpSystemHelper, modAuditLogWriter, database)); + features.add(new HelpThreadAutoArchiver(helpSystemHelper)); // Message receivers features.add(new TopHelpersMessageListener(database, config)); diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/help/HelpSystemHelper.java b/application/src/main/java/org/togetherjava/tjbot/commands/help/HelpSystemHelper.java index 2d2e49114c..b30c1a56d9 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/help/HelpSystemHelper.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/help/HelpSystemHelper.java @@ -20,6 +20,7 @@ import java.util.List; import java.util.Locale; import java.util.Optional; +import java.util.function.Consumer; import java.util.function.Predicate; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -214,8 +215,8 @@ static boolean isTitleValid(@NotNull CharSequence title) { } @NotNull - Optional handleRequireOverviewChannelForAsk(@NotNull Guild guild, - @NotNull MessageChannel respondTo) { + Optional handleRequireOverviewChannel(@NotNull Guild guild, + @NotNull Consumer consumeChannelPatternIfNotFound) { Predicate isChannelName = this::isOverviewChannelName; String channelPattern = getOverviewChannelPattern(); @@ -225,6 +226,16 @@ Optional handleRequireOverviewChannelForAsk(@NotNull Guild guild, .findAny(); if (maybeChannel.isEmpty()) { + consumeChannelPatternIfNotFound.accept(channelPattern); + } + + return maybeChannel; + } + + @NotNull + Optional handleRequireOverviewChannelForAsk(@NotNull Guild guild, + @NotNull MessageChannel respondTo) { + return handleRequireOverviewChannel(guild, channelPattern -> { logger.warn( "Attempted to create a help thread, did not find the overview channel matching the configured pattern '{}' for guild '{}'", channelPattern, guild.getName()); @@ -232,10 +243,15 @@ Optional handleRequireOverviewChannelForAsk(@NotNull Guild guild, respondTo.sendMessage( "Sorry, I was unable to locate the overview channel. The server seems wrongly configured, please contact a moderator.") .queue(); - return Optional.empty(); - } + }); + } - return maybeChannel; + @NotNull + List getActiveThreadsIn(@NotNull TextChannel channel) { + return channel.getThreadChannels() + .stream() + .filter(Predicate.not(ThreadChannel::isArchived)) + .toList(); } record HelpThreadName(@Nullable ThreadActivity activity, @Nullable String category, diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/help/HelpThreadActivityUpdater.java b/application/src/main/java/org/togetherjava/tjbot/commands/help/HelpThreadActivityUpdater.java index 3114410ff4..f21cad798b 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/help/HelpThreadActivityUpdater.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/help/HelpThreadActivityUpdater.java @@ -48,7 +48,10 @@ public void runRoutine(@NotNull JDA jda) { } private void updateActivityForGuild(@NotNull Guild guild) { - Optional maybeOverviewChannel = handleRequireOverviewChannel(guild); + Optional maybeOverviewChannel = helper + .handleRequireOverviewChannel(guild, channelPattern -> logger.warn( + "Unable to update help thread overview, did not find an overview channel matching the configured pattern '{}' for guild '{}'", + channelPattern, guild.getName())); if (maybeOverviewChannel.isEmpty()) { return; @@ -56,36 +59,13 @@ private void updateActivityForGuild(@NotNull Guild guild) { logger.debug("Updating activities of active questions"); - List activeThreads = maybeOverviewChannel.orElseThrow() - .getThreadChannels() - .stream() - .filter(Predicate.not(ThreadChannel::isArchived)) - .toList(); - + List activeThreads = + helper.getActiveThreadsIn(maybeOverviewChannel.orElseThrow()); logger.debug("Found {} active questions", activeThreads.size()); activeThreads.forEach(this::updateActivityForThread); } - private @NotNull Optional handleRequireOverviewChannel(@NotNull Guild guild) { - Predicate isChannelName = helper::isOverviewChannelName; - String channelPattern = helper.getOverviewChannelPattern(); - - Optional maybeChannel = guild.getTextChannelCache() - .stream() - .filter(channel -> isChannelName.test(channel.getName())) - .findAny(); - - if (maybeChannel.isEmpty()) { - logger.warn( - "Unable to update help thread overview, did not find an overview channel matching the configured pattern '{}' for guild '{}'", - channelPattern, guild.getName()); - return Optional.empty(); - } - - return maybeChannel; - } - private void updateActivityForThread(@NotNull ThreadChannel threadChannel) { determineActivity(threadChannel) .flatMap( diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/help/HelpThreadAutoArchiver.java b/application/src/main/java/org/togetherjava/tjbot/commands/help/HelpThreadAutoArchiver.java new file mode 100644 index 0000000000..00b093956c --- /dev/null +++ b/application/src/main/java/org/togetherjava/tjbot/commands/help/HelpThreadAutoArchiver.java @@ -0,0 +1,100 @@ +package org.togetherjava.tjbot.commands.help; + +import net.dv8tion.jda.api.EmbedBuilder; +import net.dv8tion.jda.api.JDA; +import net.dv8tion.jda.api.entities.*; +import net.dv8tion.jda.api.utils.TimeUtil; +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.togetherjava.tjbot.commands.Routine; + +import java.time.Duration; +import java.time.Instant; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.TimeUnit; + +/** + * Routine, which periodically checks all help threads and archives them if there has not been any + * recent activity. + */ +public final class HelpThreadAutoArchiver implements Routine { + private static final Logger logger = LoggerFactory.getLogger(HelpThreadAutoArchiver.class); + private static final int SCHEDULE_MINUTES = 60; + private static final Duration ARCHIVE_AFTER_INACTIVITY_OF = Duration.ofHours(24); + + private final HelpSystemHelper helper; + + /** + * Creates a new instance. + * + * @param helper the helper to use + */ + public HelpThreadAutoArchiver(@NotNull HelpSystemHelper helper) { + this.helper = helper; + } + + @Override + public @NotNull Schedule createSchedule() { + return new Schedule(ScheduleMode.FIXED_RATE, 0, SCHEDULE_MINUTES, TimeUnit.MINUTES); + } + + @Override + public void runRoutine(@NotNull JDA jda) { + jda.getGuildCache().forEach(this::autoArchiveForGuild); + } + + private void autoArchiveForGuild(@NotNull Guild guild) { + Optional maybeOverviewChannel = helper + .handleRequireOverviewChannel(guild, channelPattern -> logger.warn( + "Unable to auto archive help threads, did not find an overview channel matching the configured pattern '{}' for guild '{}'", + channelPattern, guild.getName())); + + if (maybeOverviewChannel.isEmpty()) { + return; + } + + logger.debug("Auto archiving of help threads"); + + List activeThreads = + helper.getActiveThreadsIn(maybeOverviewChannel.orElseThrow()); + logger.debug("Found {} active questions", activeThreads.size()); + + Instant archiveAfterMoment = computeArchiveAfterMoment(); + activeThreads + .forEach(activeThread -> autoArchiveForThread(activeThread, archiveAfterMoment)); + } + + private @NotNull Instant computeArchiveAfterMoment() { + return Instant.now().minus(ARCHIVE_AFTER_INACTIVITY_OF); + } + + private void autoArchiveForThread(@NotNull ThreadChannel threadChannel, + @NotNull Instant archiveAfterMoment) { + if (shouldBeArchived(threadChannel, archiveAfterMoment)) { + logger.debug("Auto archiving help thread {}", threadChannel.getId()); + + MessageEmbed embed = new EmbedBuilder().setDescription( + """ + Closed the thread due to inactivity. + + If your question was not resolved yet, feel free to create a new thread. \ + But try to improve the quality of your question to make it easier to help you 👍""") + .setColor(HelpSystemHelper.AMBIENT_COLOR) + .build(); + + threadChannel.sendMessageEmbeds(embed) + .flatMap(any -> threadChannel.getManager().setArchived(true)) + .queue(); + } + } + + private static boolean shouldBeArchived(MessageChannel channel, + @NotNull Instant archiveAfterMoment) { + Instant lastActivity = + TimeUtil.getTimeCreated(channel.getLatestMessageIdLong()).toInstant(); + + return lastActivity.isBefore(archiveAfterMoment); + } +} diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/help/HelpThreadOverviewUpdater.java b/application/src/main/java/org/togetherjava/tjbot/commands/help/HelpThreadOverviewUpdater.java index aaac70c55c..89eafc0292 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/help/HelpThreadOverviewUpdater.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/help/HelpThreadOverviewUpdater.java @@ -19,16 +19,15 @@ import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Predicate; import java.util.regex.Pattern; import java.util.stream.Collectors; /** * Provides and updates an overview of all active questions in an overview channel. - * + *

    * The process runs on a schedule, but is also triggered whenever a new question has been asked in * the staging channel. - * + *

    * Active questions are automatically picked up and grouped by categories. */ public final class HelpThreadOverviewUpdater extends MessageReceiverAdapter implements Routine { @@ -96,7 +95,10 @@ public void onMessageReceived(@NotNull MessageReceivedEvent event) { } private void updateOverviewForGuild(@NotNull Guild guild) { - Optional maybeOverviewChannel = handleRequireOverviewChannel(guild); + Optional maybeOverviewChannel = helper + .handleRequireOverviewChannel(guild, channelPattern -> logger.warn( + "Unable to update help thread overview, did not find an overview channel matching the configured pattern '{}' for guild '{}'", + channelPattern, guild.getName())); if (maybeOverviewChannel.isEmpty()) { return; @@ -105,33 +107,10 @@ private void updateOverviewForGuild(@NotNull Guild guild) { updateOverview(maybeOverviewChannel.orElseThrow()); } - private @NotNull Optional handleRequireOverviewChannel(@NotNull Guild guild) { - Predicate isChannelName = helper::isOverviewChannelName; - String channelPattern = helper.getOverviewChannelPattern(); - - Optional maybeChannel = guild.getTextChannelCache() - .stream() - .filter(channel -> isChannelName.test(channel.getName())) - .findAny(); - - if (maybeChannel.isEmpty()) { - logger.warn( - "Unable to update help thread overview, did not find an overview channel matching the configured pattern '{}' for guild '{}'", - channelPattern, guild.getName()); - return Optional.empty(); - } - - return maybeChannel; - } - private void updateOverview(@NotNull TextChannel overviewChannel) { logger.debug("Updating overview of active questions"); - List activeThreads = overviewChannel.getThreadChannels() - .stream() - .filter(Predicate.not(ThreadChannel::isArchived)) - .toList(); - + List activeThreads = helper.getActiveThreadsIn(overviewChannel); logger.debug("Found {} active questions", activeThreads.size()); Message message = new MessageBuilder() From 1b178bacce35c629217f4bce76af26889f3f0883 Mon Sep 17 00:00:00 2001 From: Daniel Tischner Date: Sun, 28 Aug 2022 10:31:51 +0200 Subject: [PATCH 17/18] Introduce @ParametersAreNonnullByDefault (#541) * Cleaned up Nonnull/Nullable mess * Fixed some issues * Spotless after rebase * Fixes after rebase --- application/build.gradle | 1 + .../org/togetherjava/tjbot/Application.java | 10 +- .../togetherjava/tjbot/commands/Features.java | 6 +- .../tjbot/commands/MessageReceiver.java | 8 +- .../commands/MessageReceiverAdapter.java | 11 ++- .../togetherjava/tjbot/commands/Routine.java | 9 +- .../tjbot/commands/SlashCommand.java | 10 +- .../tjbot/commands/SlashCommandAdapter.java | 42 ++++---- .../tjbot/commands/UserInteractor.java | 10 +- .../tjbot/commands/basic/PingCommand.java | 3 +- .../commands/basic/RoleSelectCommand.java | 44 ++++----- .../basic/SuggestionsUpDownVoter.java | 16 +-- .../commands/basic/VcActivityCommand.java | 29 +++--- .../tjbot/commands/basic/package-info.java | 3 + .../commands/componentids/ComponentId.java | 3 +- .../componentids/ComponentIdGenerator.java | 9 +- .../componentids/ComponentIdParser.java | 11 +-- .../componentids/ComponentIdStore.java | 30 +++--- .../commands/componentids/package-info.java | 5 +- .../FileSharingMessageListener.java | 30 +++--- .../tjbot/commands/filesharing/GistFile.java | 4 +- .../tjbot/commands/filesharing/GistFiles.java | 3 +- .../commands/filesharing/GistRequest.java | 4 +- .../commands/filesharing/GistResponse.java | 10 +- .../commands/filesharing/package-info.java | 3 + .../tjbot/commands/help/AskCommand.java | 28 +++--- .../commands/help/AutoPruneHelperRoutine.java | 29 +++--- .../commands/help/BotMessageCleanup.java | 21 ++-- .../help/ChangeHelpCategoryCommand.java | 14 +-- .../commands/help/ChangeHelpTitleCommand.java | 9 +- .../tjbot/commands/help/CloseCommand.java | 5 +- .../tjbot/commands/help/HelpSystemHelper.java | 85 ++++++++-------- .../help/HelpThreadActivityUpdater.java | 18 ++-- .../commands/help/HelpThreadAutoArchiver.java | 20 ++-- .../help/HelpThreadMetadataPurger.java | 10 +- .../help/HelpThreadOverviewUpdater.java | 39 ++++---- .../commands/help/ImplicitAskListener.java | 34 ++++--- .../help/OnGuildLeaveCloseThreadListener.java | 11 +-- .../tjbot/commands/help/package-info.java | 3 + .../commands/mathcommands/TeXCommand.java | 25 +++-- .../wolframalpha/WolframAlphaCommand.java | 9 +- .../wolframalpha/WolframAlphaHandler.java | 33 ++++--- .../wolframalpha/WolframAlphaImages.java | 24 +++-- .../wolframalpha/api/package-info.java | 3 + .../wolframalpha/package-info.java | 3 + .../commands/moderation/ActionRecord.java | 13 ++- .../commands/moderation/AuditCommand.java | 59 ++++++----- .../tjbot/commands/moderation/BanCommand.java | 52 +++++----- .../commands/moderation/KickCommand.java | 39 ++++---- .../commands/moderation/ModerationAction.java | 7 +- .../moderation/ModerationActionsStore.java | 40 ++++---- .../commands/moderation/ModerationUtils.java | 68 ++++++------- .../commands/moderation/MuteCommand.java | 42 ++++---- .../commands/moderation/NoteCommand.java | 23 ++--- .../moderation/QuarantineCommand.java | 36 +++---- .../RejoinModerationRoleListener.java | 19 ++-- .../commands/moderation/UnbanCommand.java | 19 ++-- .../commands/moderation/UnmuteCommand.java | 36 +++---- .../moderation/UnquarantineCommand.java | 37 ++++--- .../commands/moderation/WarnCommand.java | 33 +++---- .../commands/moderation/WhoIsCommand.java | 51 +++++----- .../commands/moderation/package-info.java | 3 + .../commands/moderation/scam/ScamBlocker.java | 68 ++++++------- .../moderation/scam/ScamDetector.java | 13 ++- .../scam/ScamHistoryPurgeRoutine.java | 9 +- .../moderation/scam/ScamHistoryStore.java | 24 ++--- .../moderation/scam/package-info.java | 3 + .../temp/RevocableModerationAction.java | 16 +-- .../temp/RevocableRoleBasedAction.java | 9 +- .../moderation/temp/TemporaryBanAction.java | 17 ++-- .../temp/TemporaryModerationRoutine.java | 31 +++--- .../moderation/temp/TemporaryMuteAction.java | 15 +-- .../temp/TemporaryQuarantineAction.java | 15 +-- .../moderation/temp/package-info.java | 3 + .../tjbot/commands/package-info.java | 3 + .../commands/reminder/RemindCommand.java | 16 +-- .../commands/reminder/RemindRoutine.java | 53 +++++----- .../tjbot/commands/reminder/package-info.java | 3 + .../tjbot/commands/system/BotCore.java | 42 ++++---- .../commands/system/LogLevelCommand.java | 3 +- .../tjbot/commands/system/ReloadCommand.java | 24 ++--- .../commands/system/SlashCommandProvider.java | 8 +- .../tjbot/commands/system/package-info.java | 3 + .../tjbot/commands/tags/TagCommand.java | 3 +- .../tjbot/commands/tags/TagManageCommand.java | 69 ++++++------- .../tjbot/commands/tags/TagSystem.java | 9 +- .../tjbot/commands/tags/TagsCommand.java | 5 +- .../tjbot/commands/tags/package-info.java | 3 + .../commands/tophelper/TopHelpersCommand.java | 46 ++++----- .../tophelper/TopHelpersMessageListener.java | 9 +- .../TopHelpersPurgeMessagesRoutine.java | 9 +- .../commands/tophelper/package-info.java | 3 + .../commands/utils/DiscordClientAction.java | 9 +- .../tjbot/commands/utils/Hashing.java | 11 +-- .../tjbot/commands/utils/MessageUtils.java | 7 +- .../tjbot/commands/utils/StringDistances.java | 42 ++++---- .../tjbot/commands/utils/package-info.java | 3 + .../org/togetherjava/tjbot/config/Config.java | 18 ++-- .../tjbot/config/HelpSystemConfig.java | 14 ++- .../tjbot/config/ScamBlockerConfig.java | 14 ++- .../tjbot/config/package-info.java | 3 + .../tjbot/logging/FlaggedFilter.java | 11 ++- .../tjbot/logging/package-info.java | 3 + .../tjbot/moderation/ModAuditLogWriter.java | 21 ++-- .../org/togetherjava/tjbot/package-info.java | 3 + .../tjbot/routines/ModAuditLogRoutine.java | 93 ++++++++--------- .../tjbot/routines/package-info.java | 3 + .../commands/SlashCommandAdapterTest.java | 3 +- .../tjbot/commands/basic/PingCommandTest.java | 6 +- .../commands/mathcommands/TeXCommandTest.java | 14 +-- .../moderation/scam/ScamDetectorTest.java | 7 +- .../reminder/RawReminderTestHelper.java | 18 ++-- .../commands/reminder/RemindCommandTest.java | 12 ++- .../commands/reminder/RemindRoutineTest.java | 13 ++- .../commands/system/ReloadCommandTest.java | 8 +- .../tjbot/commands/tags/TagCommandTest.java | 8 +- .../commands/tags/TagManageCommandTest.java | 49 ++++----- .../tjbot/commands/tags/TagsCommandTest.java | 15 +-- .../commands/utils/StringDistancesTest.java | 9 +- .../tjbot/jda/ButtonClickEventBuilder.java | 54 +++++----- .../org/togetherjava/tjbot/jda/JdaTester.java | 62 +++++++----- .../SlashCommandInteractionEventBuilder.java | 99 ++++++++++--------- .../togetherjava/tjbot/jda/package-info.java | 3 + .../tjbot/jda/payloads/PayloadMember.java | 32 +++--- .../tjbot/jda/payloads/PayloadUser.java | 24 ++--- .../slashcommand/PayloadSlashCommand.java | 38 +++---- .../slashcommand/PayloadSlashCommandData.java | 20 ++-- .../PayloadSlashCommandMembers.java | 9 +- .../PayloadSlashCommandOption.java | 10 +- .../PayloadSlashCommandResolved.java | 8 +- .../PayloadSlashCommandUsers.java | 9 +- database/build.gradle | 1 + .../togetherjava/tjbot/db/package-info.java | 3 + .../tjbot/db/util/package-info.java | 3 + 134 files changed, 1409 insertions(+), 1231 deletions(-) diff --git a/application/build.gradle b/application/build.gradle index c36956258d..80b59cb2a8 100644 --- a/application/build.gradle +++ b/application/build.gradle @@ -39,6 +39,7 @@ shadowJar { } dependencies { + implementation 'com.google.code.findbugs:jsr305:3.0.2' implementation 'org.jetbrains:annotations:23.0.0' implementation project(':database') diff --git a/application/src/main/java/org/togetherjava/tjbot/Application.java b/application/src/main/java/org/togetherjava/tjbot/Application.java index 73b0b6be1a..fed19cc192 100644 --- a/application/src/main/java/org/togetherjava/tjbot/Application.java +++ b/application/src/main/java/org/togetherjava/tjbot/Application.java @@ -2,20 +2,17 @@ import net.dv8tion.jda.api.JDA; import net.dv8tion.jda.api.JDABuilder; -import net.dv8tion.jda.api.requests.GatewayIntent; import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; -import org.jetbrains.annotations.NotNull; +import net.dv8tion.jda.api.requests.GatewayIntent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - import org.togetherjava.tjbot.commands.Features; +import org.togetherjava.tjbot.commands.SlashCommandAdapter; import org.togetherjava.tjbot.commands.system.BotCore; import org.togetherjava.tjbot.config.Config; import org.togetherjava.tjbot.db.Database; -import org.togetherjava.tjbot.commands.SlashCommandAdapter; import javax.security.auth.login.LoginException; - import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; @@ -114,8 +111,7 @@ private static void onShutdown() { logger.info("Bot has been stopped"); } - private static void onUncaughtException(@NotNull Thread failingThread, - @NotNull Throwable failure) { + private static void onUncaughtException(Thread failingThread, Throwable failure) { logger.error("Unknown error in thread {}.", failingThread.getName(), failure); } diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/Features.java b/application/src/main/java/org/togetherjava/tjbot/commands/Features.java index 4a1f831fe2..0826682c08 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/Features.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/Features.java @@ -1,7 +1,6 @@ package org.togetherjava.tjbot.commands; import net.dv8tion.jda.api.JDA; -import org.jetbrains.annotations.NotNull; import org.togetherjava.tjbot.commands.basic.PingCommand; import org.togetherjava.tjbot.commands.basic.RoleSelectCommand; import org.togetherjava.tjbot.commands.basic.SuggestionsUpDownVoter; @@ -32,6 +31,7 @@ import org.togetherjava.tjbot.moderation.ModAuditLogWriter; import org.togetherjava.tjbot.routines.ModAuditLogRoutine; +import javax.annotation.Nonnull; import java.util.ArrayList; import java.util.Collection; @@ -59,8 +59,8 @@ private Features() { * @param config the configuration features should use * @return a collection of all features */ - public static @NotNull Collection createFeatures(@NotNull JDA jda, - @NotNull Database database, @NotNull Config config) { + @Nonnull + public static Collection createFeatures(JDA jda, Database database, Config config) { TagSystem tagSystem = new TagSystem(database); ModerationActionsStore actionsStore = new ModerationActionsStore(database); ModAuditLogWriter modAuditLogWriter = new ModAuditLogWriter(config); diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/MessageReceiver.java b/application/src/main/java/org/togetherjava/tjbot/commands/MessageReceiver.java index ee679a47f0..c99e08cf98 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/MessageReceiver.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/MessageReceiver.java @@ -2,8 +2,8 @@ import net.dv8tion.jda.api.events.message.MessageReceivedEvent; import net.dv8tion.jda.api.events.message.MessageUpdateEvent; -import org.jetbrains.annotations.NotNull; +import javax.annotation.Nonnull; import java.util.regex.Pattern; /** @@ -28,7 +28,7 @@ public interface MessageReceiver extends Feature { * * @return the pattern matching the names of relevant channels */ - @NotNull + @Nonnull Pattern getChannelNamePattern(); /** @@ -38,7 +38,7 @@ public interface MessageReceiver extends Feature { * @param event the event that triggered this, containing information about the corresponding * message that was sent and received */ - void onMessageReceived(@NotNull MessageReceivedEvent event); + void onMessageReceived(MessageReceivedEvent event); /** * Triggered by the core system whenever an existing message was edited in a text channel of a @@ -47,5 +47,5 @@ public interface MessageReceiver extends Feature { * @param event the event that triggered this, containing information about the corresponding * message that was edited */ - void onMessageUpdated(@NotNull MessageUpdateEvent event); + void onMessageUpdated(MessageUpdateEvent event); } diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/MessageReceiverAdapter.java b/application/src/main/java/org/togetherjava/tjbot/commands/MessageReceiverAdapter.java index 3d13b3b3e9..b2be566758 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/MessageReceiverAdapter.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/MessageReceiverAdapter.java @@ -2,8 +2,8 @@ import net.dv8tion.jda.api.events.message.MessageReceivedEvent; import net.dv8tion.jda.api.events.message.MessageUpdateEvent; -import org.jetbrains.annotations.NotNull; +import javax.annotation.Nonnull; import java.util.regex.Pattern; /** @@ -24,24 +24,25 @@ public abstract class MessageReceiverAdapter implements MessageReceiver { * @param channelNamePattern the pattern matching names of channels interested in, only messages * from matching channels will be received */ - protected MessageReceiverAdapter(@NotNull Pattern channelNamePattern) { + protected MessageReceiverAdapter(Pattern channelNamePattern) { this.channelNamePattern = channelNamePattern; } @Override - public final @NotNull Pattern getChannelNamePattern() { + @Nonnull + public final Pattern getChannelNamePattern() { return channelNamePattern; } @SuppressWarnings("NoopMethodInAbstractClass") @Override - public void onMessageReceived(@NotNull MessageReceivedEvent event) { + public void onMessageReceived(MessageReceivedEvent event) { // Adapter does not react by default, subclasses may change this behavior } @SuppressWarnings("NoopMethodInAbstractClass") @Override - public void onMessageUpdated(@NotNull MessageUpdateEvent event) { + public void onMessageUpdated(MessageUpdateEvent event) { // Adapter does not react by default, subclasses may change this behavior } } diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/Routine.java b/application/src/main/java/org/togetherjava/tjbot/commands/Routine.java index 10b8aa9d79..1d684bf6a7 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/Routine.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/Routine.java @@ -1,8 +1,8 @@ package org.togetherjava.tjbot.commands; import net.dv8tion.jda.api.JDA; -import org.jetbrains.annotations.NotNull; +import javax.annotation.Nonnull; import java.util.concurrent.TimeUnit; /** @@ -24,7 +24,7 @@ public interface Routine extends Feature { * * @return the schedule of this routine */ - @NotNull + @Nonnull Schedule createSchedule(); /** @@ -32,7 +32,7 @@ public interface Routine extends Feature { * * @param jda the JDA instance the bot is operating with */ - void runRoutine(@NotNull JDA jda); + void runRoutine(JDA jda); /** * The schedule of routines. @@ -46,8 +46,7 @@ public interface Routine extends Feature { * @param unit the time unit for both, {@link #initialDuration} and {@link #duration}, e.g. * seconds */ - record Schedule(@NotNull ScheduleMode mode, long initialDuration, long duration, - @NotNull TimeUnit unit) { + record Schedule(ScheduleMode mode, long initialDuration, long duration, TimeUnit unit) { } diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/SlashCommand.java b/application/src/main/java/org/togetherjava/tjbot/commands/SlashCommand.java index 20f5c936b7..551e4474ec 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/SlashCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/SlashCommand.java @@ -9,11 +9,11 @@ import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData; import net.dv8tion.jda.api.interactions.components.ComponentInteraction; import net.dv8tion.jda.api.interactions.components.buttons.ButtonStyle; -import org.jetbrains.annotations.NotNull; import org.togetherjava.tjbot.commands.componentids.ComponentId; import org.togetherjava.tjbot.commands.componentids.ComponentIdGenerator; import org.togetherjava.tjbot.commands.componentids.Lifespan; +import javax.annotation.Nonnull; import java.util.List; /** @@ -49,7 +49,7 @@ public interface SlashCommand extends UserInteractor { * * @return the description of the command */ - @NotNull + @Nonnull String getDescription(); /** @@ -59,7 +59,7 @@ public interface SlashCommand extends UserInteractor { * * @return the visibility of the command */ - @NotNull + @Nonnull SlashCommandVisibility getVisibility(); /** @@ -77,7 +77,7 @@ public interface SlashCommand extends UserInteractor { * * @return the command data of this command */ - @NotNull + @Nonnull SlashCommandData getData(); /** @@ -118,5 +118,5 @@ public interface SlashCommand extends UserInteractor { * * @param event the event that triggered this */ - void onSlashCommand(@NotNull SlashCommandInteractionEvent event); + void onSlashCommand(SlashCommandInteractionEvent event); } diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/SlashCommandAdapter.java b/application/src/main/java/org/togetherjava/tjbot/commands/SlashCommandAdapter.java index 023c808a1d..d0d2ee94c4 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/SlashCommandAdapter.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/SlashCommandAdapter.java @@ -9,13 +9,13 @@ import net.dv8tion.jda.api.interactions.commands.build.Commands; import net.dv8tion.jda.api.interactions.commands.build.OptionData; import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData; -import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Range; import org.jetbrains.annotations.Unmodifiable; import org.togetherjava.tjbot.commands.componentids.ComponentId; import org.togetherjava.tjbot.commands.componentids.ComponentIdGenerator; import org.togetherjava.tjbot.commands.componentids.Lifespan; +import javax.annotation.Nonnull; import java.util.Arrays; import java.util.List; import java.util.Objects; @@ -55,7 +55,7 @@ * } * * @Override - * public void onSlashCommand(@NotNull SlashCommandInteractionEvent event) { + * public void onSlashCommand(SlashCommandInteractionEvent event) { * event.reply("Pong!").queue(); * } * } @@ -80,7 +80,7 @@ public abstract class SlashCommandAdapter implements SlashCommand { * {@link SlashCommandData#setDescription(String)} * @param visibility the visibility of the command */ - protected SlashCommandAdapter(@NotNull String name, @NotNull String description, + protected SlashCommandAdapter(String name, String description, SlashCommandVisibility visibility) { this.name = name; this.description = description; @@ -90,40 +90,43 @@ protected SlashCommandAdapter(@NotNull String name, @NotNull String description, } @Override - public final @NotNull String getName() { + @Nonnull + public final String getName() { return name; } @Override - public final @NotNull String getDescription() { + @Nonnull + public final String getDescription() { return description; } @Override - public final @NotNull SlashCommandVisibility getVisibility() { + @Nonnull + public final SlashCommandVisibility getVisibility() { return visibility; } @Override - public final @NotNull SlashCommandData getData() { + @Nonnull + public final SlashCommandData getData() { return data; } @Override - public final void acceptComponentIdGenerator(@NotNull ComponentIdGenerator generator) { + public final void acceptComponentIdGenerator(ComponentIdGenerator generator) { componentIdGenerator = generator; } @SuppressWarnings("NoopMethodInAbstractClass") @Override - public void onButtonClick(@NotNull ButtonInteractionEvent event, @NotNull List args) { + public void onButtonClick(ButtonInteractionEvent event, List args) { // Adapter does not react by default, subclasses may change this behavior } @SuppressWarnings("NoopMethodInAbstractClass") @Override - public void onSelectionMenu(@NotNull SelectMenuInteractionEvent event, - @NotNull List args) { + public void onSelectionMenu(SelectMenuInteractionEvent event, List args) { // Adapter does not react by default, subclasses may change this behavior } @@ -142,7 +145,8 @@ public void onSelectionMenu(@NotNull SelectMenuInteractionEvent event, * @return the generated component ID */ @SuppressWarnings("OverloadedVarargsMethod") - protected final @NotNull String generateComponentId(@NotNull String... args) { + @Nonnull + protected final String generateComponentId(String... args) { return generateComponentId(Lifespan.REGULAR, args); } @@ -159,8 +163,8 @@ public void onSelectionMenu(@NotNull SelectMenuInteractionEvent event, * @return the generated component ID */ @SuppressWarnings({"OverloadedVarargsMethod", "WeakerAccess"}) - protected final @NotNull String generateComponentId(@NotNull Lifespan lifespan, - @NotNull String... args) { + @Nonnull + protected final String generateComponentId(Lifespan lifespan, String... args) { return Objects.requireNonNull(componentIdGenerator) .generate(new ComponentId(getName(), Arrays.asList(args)), lifespan); } @@ -190,8 +194,9 @@ public void onSelectionMenu(@NotNull SelectMenuInteractionEvent event, * @return the generated list of options */ @Unmodifiable - protected static @NotNull List generateMultipleOptions( - @NotNull OptionData optionData, @Range(from = 1, to = 25) int amount) { + @Nonnull + protected static List generateMultipleOptions(OptionData optionData, + @Range(from = 1, to = 25) int amount) { String baseName = optionData.getName(); Function nameToOption = @@ -211,8 +216,9 @@ public void onSelectionMenu(@NotNull SelectMenuInteractionEvent event, * @return all options with the given prefix */ @Unmodifiable - protected static @NotNull List getMultipleOptionsByNamePrefix( - @NotNull CommandInteractionPayload event, @NotNull String namePrefix) { + @Nonnull + protected static List getMultipleOptionsByNamePrefix( + CommandInteractionPayload event, String namePrefix) { return event.getOptions() .stream() .filter(option -> option.getName().startsWith(namePrefix)) diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/UserInteractor.java b/application/src/main/java/org/togetherjava/tjbot/commands/UserInteractor.java index 4ad0ed0da2..68a916369c 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/UserInteractor.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/UserInteractor.java @@ -3,9 +3,9 @@ import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent; import net.dv8tion.jda.api.events.interaction.component.SelectMenuInteractionEvent; -import org.jetbrains.annotations.NotNull; import org.togetherjava.tjbot.commands.componentids.ComponentIdGenerator; +import javax.annotation.Nonnull; import java.util.List; /** @@ -27,7 +27,7 @@ public interface UserInteractor extends Feature { * * @return the name of the interactor */ - @NotNull + @Nonnull String getName(); /** @@ -49,7 +49,7 @@ public interface UserInteractor extends Feature { * {@link SlashCommand#onSlashCommand(SlashCommandInteractionEvent)} for details on how * these are created */ - void onButtonClick(@NotNull ButtonInteractionEvent event, @NotNull List args); + void onButtonClick(ButtonInteractionEvent event, List args); /** * Triggered by the core system when a selection menu corresponding to this implementation @@ -70,7 +70,7 @@ public interface UserInteractor extends Feature { * {@link SlashCommand#onSlashCommand(SlashCommandInteractionEvent)} for details on how * these are created */ - void onSelectionMenu(@NotNull SelectMenuInteractionEvent event, @NotNull List args); + void onSelectionMenu(SelectMenuInteractionEvent event, List args); /** * Triggered by the core system during its setup phase. It will provide the interactor a @@ -81,5 +81,5 @@ public interface UserInteractor extends Feature { * * @param generator the provided component id generator */ - void acceptComponentIdGenerator(@NotNull ComponentIdGenerator generator); + void acceptComponentIdGenerator(ComponentIdGenerator generator); } diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/basic/PingCommand.java b/application/src/main/java/org/togetherjava/tjbot/commands/basic/PingCommand.java index 17928dbd7f..fdaba47f5b 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/basic/PingCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/basic/PingCommand.java @@ -1,7 +1,6 @@ package org.togetherjava.tjbot.commands.basic; import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; -import org.jetbrains.annotations.NotNull; import org.togetherjava.tjbot.commands.SlashCommandAdapter; import org.togetherjava.tjbot.commands.SlashCommandVisibility; @@ -24,7 +23,7 @@ public PingCommand() { * @param event the corresponding event */ @Override - public void onSlashCommand(@NotNull SlashCommandInteractionEvent event) { + public void onSlashCommand(SlashCommandInteractionEvent event) { event.reply("Pong!").queue(); } } diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/basic/RoleSelectCommand.java b/application/src/main/java/org/togetherjava/tjbot/commands/basic/RoleSelectCommand.java index 0bf97b93f8..be5550bdb9 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/basic/RoleSelectCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/basic/RoleSelectCommand.java @@ -13,13 +13,13 @@ import net.dv8tion.jda.api.interactions.components.selections.SelectMenu; import net.dv8tion.jda.api.interactions.components.selections.SelectOption; import org.jetbrains.annotations.Contract; -import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.togetherjava.tjbot.commands.SlashCommandAdapter; import org.togetherjava.tjbot.commands.SlashCommandVisibility; import org.togetherjava.tjbot.commands.componentids.Lifespan; +import javax.annotation.Nonnull; import java.awt.Color; import java.util.*; import java.util.function.Function; @@ -80,7 +80,7 @@ public RoleSelectCommand() { } @Override - public void onSlashCommand(@NotNull SlashCommandInteractionEvent event) { + public void onSlashCommand(SlashCommandInteractionEvent event) { if (!handleHasPermissions(event)) { return; } @@ -101,7 +101,7 @@ public void onSlashCommand(@NotNull SlashCommandInteractionEvent event) { sendRoleSelectionMenu(event, selectedRoles); } - private boolean handleHasPermissions(@NotNull SlashCommandInteractionEvent event) { + private boolean handleHasPermissions(SlashCommandInteractionEvent event) { if (!event.getMember().hasPermission(Permission.MANAGE_ROLES)) { event.reply("You do not have the required manage role permission to use this command") .setEphemeral(true) @@ -123,7 +123,7 @@ private boolean handleHasPermissions(@NotNull SlashCommandInteractionEvent event } @Contract(pure = true) - private static boolean handleIsBotAccessibleRole(@NotNull Role role) { + private static boolean handleIsBotAccessibleRole(Role role) { boolean isSystemRole = role.isPublicRole() || role.getTags().isBot() || role.getTags().isBoost() || role.getTags().isIntegration(); @@ -135,8 +135,8 @@ private static boolean handleIsBotAccessibleRole(@NotNull Role role) { return !isSystemRole; } - private static boolean handleAccessibleRolesSelected(@NotNull IReplyCallback event, - @NotNull Collection selectedRoles) { + private static boolean handleAccessibleRolesSelected(IReplyCallback event, + Collection selectedRoles) { if (!selectedRoles.isEmpty()) { return true; } @@ -151,8 +151,8 @@ private static boolean handleAccessibleRolesSelected(@NotNull IReplyCallback eve return false; } - private static boolean handleInteractableRolesSelected(@NotNull IReplyCallback event, - @NotNull Collection selectedRoles) { + private static boolean handleInteractableRolesSelected(IReplyCallback event, + Collection selectedRoles) { List nonInteractableRoles = selectedRoles.stream() .filter(role -> !event.getGuild().getSelfMember().canInteract(role)) .toList(); @@ -173,8 +173,8 @@ private static boolean handleInteractableRolesSelected(@NotNull IReplyCallback e return false; } - private void sendRoleSelectionMenu(@NotNull final CommandInteraction event, - @NotNull final Collection selectableRoles) { + private void sendRoleSelectionMenu(final CommandInteraction event, + final Collection selectableRoles) { SelectMenu.Builder menu = SelectMenu.create(generateComponentId(Lifespan.PERMANENT, event.getUser().getId())) .setPlaceholder("Select your roles") @@ -193,8 +193,8 @@ private void sendRoleSelectionMenu(@NotNull final CommandInteraction event, event.replyEmbeds(embed).addActionRow(menu.build()).queue(); } - @NotNull - private static SelectOption mapToSelectOption(@NotNull Role role) { + @Nonnull + private static SelectOption mapToSelectOption(Role role) { RoleIcon roleIcon = role.getIcon(); SelectOption option = SelectOption.of(role.getName(), role.getId()); @@ -206,8 +206,7 @@ private static SelectOption mapToSelectOption(@NotNull Role role) { } @Override - public void onSelectionMenu(@NotNull SelectMenuInteractionEvent event, - @NotNull List args) { + public void onSelectionMenu(SelectMenuInteractionEvent event, List args) { Guild guild = event.getGuild(); List selectedRoles = event.getSelectedOptions() .stream() @@ -223,8 +222,8 @@ public void onSelectionMenu(@NotNull SelectMenuInteractionEvent event, handleRoleSelection(event, guild, selectedRoles); } - private static void handleRoleSelection(@NotNull SelectMenuInteractionEvent event, - @NotNull Guild guild, @NotNull Collection selectedRoles) { + private static void handleRoleSelection(SelectMenuInteractionEvent event, Guild guild, + Collection selectedRoles) { Collection rolesToAdd = new ArrayList<>(selectedRoles.size()); Collection rolesToRemove = new ArrayList<>(selectedRoles.size()); @@ -244,8 +243,8 @@ private static void handleRoleSelection(@NotNull SelectMenuInteractionEvent even modifyRoles(event, event.getMember(), guild, rolesToAdd, rolesToRemove); } - @NotNull - private static Function> optionToRole(@NotNull Guild guild) { + @Nonnull + private static Function> optionToRole(Guild guild) { return option -> { Role role = guild.getRoleById(option.getValue()); @@ -259,16 +258,15 @@ private static Function> optionToRole(@NotNull Guil }; } - private static void modifyRoles(@NotNull IReplyCallback event, @NotNull Member target, - @NotNull Guild guild, @NotNull Collection rolesToAdd, - @NotNull Collection rolesToRemove) { + private static void modifyRoles(IReplyCallback event, Member target, Guild guild, + Collection rolesToAdd, Collection rolesToRemove) { guild.modifyMemberRoles(target, rolesToAdd, rolesToRemove) .flatMap(empty -> event.reply("Your roles have been updated.").setEphemeral(true)) .queue(); } - private static @NotNull MessageEmbed createEmbed(@NotNull String title, - @NotNull CharSequence description) { + @Nonnull + private static MessageEmbed createEmbed(String title, CharSequence description) { return new EmbedBuilder().setTitle(title) .setDescription(description) .setColor(AMBIENT_COLOR) diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/basic/SuggestionsUpDownVoter.java b/application/src/main/java/org/togetherjava/tjbot/commands/basic/SuggestionsUpDownVoter.java index 280f32599e..84bd30ece1 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/basic/SuggestionsUpDownVoter.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/basic/SuggestionsUpDownVoter.java @@ -6,13 +6,13 @@ import net.dv8tion.jda.api.events.message.MessageReceivedEvent; import net.dv8tion.jda.api.exceptions.ErrorResponseException; import net.dv8tion.jda.api.requests.ErrorResponse; -import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.togetherjava.tjbot.commands.MessageReceiverAdapter; import org.togetherjava.tjbot.config.Config; import org.togetherjava.tjbot.config.SuggestionsConfig; +import javax.annotation.Nonnull; import java.util.Optional; import java.util.regex.Pattern; @@ -33,14 +33,14 @@ public final class SuggestionsUpDownVoter extends MessageReceiverAdapter { * * @param config the config to use for this */ - public SuggestionsUpDownVoter(@NotNull Config config) { + public SuggestionsUpDownVoter(Config config) { super(Pattern.compile(config.getSuggestions().getChannelPattern())); this.config = config.getSuggestions(); } @Override - public void onMessageReceived(@NotNull MessageReceivedEvent event) { + public void onMessageReceived(MessageReceivedEvent event) { if (event.getAuthor().isBot() || event.isWebhookMessage() || !event.isFromGuild()) { return; } @@ -53,7 +53,7 @@ public void onMessageReceived(@NotNull MessageReceivedEvent event) { reactWith(config.getDownVoteEmoteName(), FALLBACK_DOWN_VOTE, guild, message); } - private static void createThread(@NotNull Message message) { + private static void createThread(Message message) { String title = message.getContentRaw(); if (title.length() >= TITLE_MAX_LENGTH) { @@ -69,8 +69,8 @@ private static void createThread(@NotNull Message message) { message.createThreadChannel(title).queue(); } - private static void reactWith(@NotNull String emoteName, @NotNull String fallbackUnicodeEmote, - @NotNull Guild guild, @NotNull Message message) { + private static void reactWith(String emoteName, String fallbackUnicodeEmote, Guild guild, + Message message) { getEmoteByName(emoteName, guild).map(message::addReaction).orElseGet(() -> { logger.warn( "Unable to vote on a suggestion with the configured emote ('{}'), using fallback instead.", @@ -89,8 +89,8 @@ private static void reactWith(@NotNull String emoteName, @NotNull String fallbac }); } - private static @NotNull Optional getEmoteByName(@NotNull String name, - @NotNull Guild guild) { + @Nonnull + private static Optional getEmoteByName(String name, Guild guild) { return guild.getEmotesByName(name, false).stream().findAny(); } } diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/basic/VcActivityCommand.java b/application/src/main/java/org/togetherjava/tjbot/commands/basic/VcActivityCommand.java index 7e6899684f..6339bac007 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/basic/VcActivityCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/basic/VcActivityCommand.java @@ -13,13 +13,13 @@ import net.dv8tion.jda.api.interactions.commands.build.SubcommandData; import net.dv8tion.jda.api.requests.restaction.interactions.ReplyCallbackAction; import org.jetbrains.annotations.Contract; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.togetherjava.tjbot.commands.SlashCommandAdapter; import org.togetherjava.tjbot.commands.SlashCommandVisibility; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; import java.util.List; import java.util.Map; import java.util.Objects; @@ -144,7 +144,7 @@ public VcActivityCommand() { @Override - public void onSlashCommand(@NotNull SlashCommandInteractionEvent event) { + public void onSlashCommand(SlashCommandInteractionEvent event) { Member member = Objects.requireNonNull(event.getMember(), "member is null"); GuildVoiceState voiceState = Objects.requireNonNull(member.getVoiceState(), "Voicestates aren't being cached, check the JDABuilder"); @@ -191,9 +191,8 @@ public void onSlashCommand(@NotNull SlashCommandInteractionEvent event) { handleSubcommand(event, voiceChannel, applicationId, maxUses, maxAgeDays, applicationName); } - - private static @NotNull Optional getKeyByValue(@NotNull Map map, - @NotNull V value) { + @Nonnull + private static Optional getKeyByValue(Map map, V value) { for (Map.Entry entry : map.entrySet()) { if (value.equals(entry.getKey())) { return Optional.of(entry.getKey()); @@ -203,10 +202,9 @@ public void onSlashCommand(@NotNull SlashCommandInteractionEvent event) { return Optional.empty(); } - private static void handleSubcommand(@NotNull SlashCommandInteractionEvent event, - @NotNull VoiceChannel voiceChannel, @NotNull String applicationId, - @Nullable Integer maxUses, @Nullable Integer maxAgeDays, - @NotNull String applicationName) { + private static void handleSubcommand(SlashCommandInteractionEvent event, + VoiceChannel voiceChannel, String applicationId, @Nullable Integer maxUses, + @Nullable Integer maxAgeDays, String applicationName) { voiceChannel.createInvite() @@ -219,9 +217,9 @@ private static void handleSubcommand(@NotNull SlashCommandInteractionEvent event } - private static @NotNull ReplyCallbackAction replyInvite( - @NotNull SlashCommandInteractionEvent event, @NotNull Invite invite, - @NotNull String applicationName) { + @Nonnull + private static ReplyCallbackAction replyInvite(SlashCommandInteractionEvent event, + Invite invite, String applicationName) { return event.reply(""" %s wants to start %s. Feel free to join by clicking %s , enjoy! @@ -229,7 +227,7 @@ private static void handleSubcommand(@NotNull SlashCommandInteractionEvent event """.formatted(event.getUser().getAsTag(), applicationName, invite.getUrl())); } - private static void handleErrors(@NotNull SlashCommandInteractionEvent event, + private static void handleErrors(SlashCommandInteractionEvent event, @Nullable Throwable throwable) { event.reply("Something went wrong :/").queue(); logger.warn("Something went wrong in the VcActivityCommand", throwable); @@ -242,7 +240,8 @@ private static void handleErrors(@NotNull SlashCommandInteractionEvent event, * @return the extracted integer if present, null otherwise **/ @Contract("null -> null") - private static @Nullable Integer requireIntOptionIfPresent(@Nullable OptionMapping option) { + @Nullable + private static Integer requireIntOptionIfPresent(@Nullable OptionMapping option) { return option == null ? null : Math.toIntExact(option.getAsLong()); diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/basic/package-info.java b/application/src/main/java/org/togetherjava/tjbot/commands/basic/package-info.java index 3394e9bcb5..177ae9c4c3 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/basic/package-info.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/basic/package-info.java @@ -2,4 +2,7 @@ * This package offers some basic commands that act as example for how to use the command system of * the application. */ +@ParametersAreNonnullByDefault package org.togetherjava.tjbot.commands.basic; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/componentids/ComponentId.java b/application/src/main/java/org/togetherjava/tjbot/commands/componentids/ComponentId.java index c5205e258b..5e297c7576 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/componentids/ComponentId.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/componentids/ComponentId.java @@ -1,6 +1,5 @@ package org.togetherjava.tjbot.commands.componentids; -import org.jetbrains.annotations.NotNull; import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import java.util.List; @@ -14,5 +13,5 @@ * this component ID, when triggered * @param elements the additional elements to carry along this component ID, empty if not desired */ -public record ComponentId(@NotNull String userInteractorName, @NotNull List elements) { +public record ComponentId(String userInteractorName, List elements) { } diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/componentids/ComponentIdGenerator.java b/application/src/main/java/org/togetherjava/tjbot/commands/componentids/ComponentIdGenerator.java index 7514983890..91cd7a3228 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/componentids/ComponentIdGenerator.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/componentids/ComponentIdGenerator.java @@ -3,12 +3,11 @@ import net.dv8tion.jda.api.entities.Emoji; import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import net.dv8tion.jda.api.interactions.components.ComponentInteraction; -import net.dv8tion.jda.api.interactions.components.buttons.ButtonStyle; import net.dv8tion.jda.api.interactions.components.buttons.Button; - +import net.dv8tion.jda.api.interactions.components.buttons.ButtonStyle; import org.togetherjava.tjbot.commands.SlashCommand; -import org.jetbrains.annotations.NotNull; +import javax.annotation.Nonnull; /** * Provides component ID generation. @@ -38,6 +37,6 @@ public interface ComponentIdGenerator { * @throws InvalidComponentIdFormatException if the given component ID was in an unexpected * format and could not be serialized */ - @NotNull - String generate(@NotNull ComponentId componentId, @NotNull Lifespan lifespan); + @Nonnull + String generate(ComponentId componentId, Lifespan lifespan); } diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/componentids/ComponentIdParser.java b/application/src/main/java/org/togetherjava/tjbot/commands/componentids/ComponentIdParser.java index 5dbfcc4374..6d5071c787 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/componentids/ComponentIdParser.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/componentids/ComponentIdParser.java @@ -1,13 +1,12 @@ package org.togetherjava.tjbot.commands.componentids; import net.dv8tion.jda.api.entities.Emoji; -import net.dv8tion.jda.api.interactions.components.ComponentInteraction; import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; -import net.dv8tion.jda.api.interactions.components.buttons.ButtonStyle; +import net.dv8tion.jda.api.interactions.components.ComponentInteraction; import net.dv8tion.jda.api.interactions.components.buttons.Button; +import net.dv8tion.jda.api.interactions.components.buttons.ButtonStyle; -import org.jetbrains.annotations.NotNull; - +import javax.annotation.Nonnull; import java.util.Optional; /** @@ -39,6 +38,6 @@ public interface ComponentIdParser { * @throws InvalidComponentIdFormatException if the component ID associated to the given UUID * was in an unexpected format and could not be deserialized */ - @NotNull - Optional parse(@NotNull String uuid); + @Nonnull + Optional parse(String uuid); } diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/componentids/ComponentIdStore.java b/application/src/main/java/org/togetherjava/tjbot/commands/componentids/ComponentIdStore.java index 7f93b79961..882505d8ba 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/componentids/ComponentIdStore.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/componentids/ComponentIdStore.java @@ -5,15 +5,15 @@ import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; -import org.togetherjava.tjbot.commands.SlashCommand; -import org.jetbrains.annotations.NotNull; import org.jooq.Result; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.togetherjava.tjbot.commands.SlashCommand; import org.togetherjava.tjbot.db.Database; import org.togetherjava.tjbot.db.generated.tables.ComponentIds; import org.togetherjava.tjbot.db.generated.tables.records.ComponentIdsRecord; +import javax.annotation.Nonnull; import java.time.Instant; import java.time.temporal.ChronoUnit; import java.time.temporal.TemporalUnit; @@ -84,7 +84,7 @@ public final class ComponentIdStore implements AutoCloseable { * * @param database the database to use to persist component IDs in */ - public ComponentIdStore(@NotNull Database database) { + public ComponentIdStore(Database database) { this(database, EVICT_DATABASE_EVERY_INITIAL_DELAY, EVICT_DATABASE_EVERY_DELAY, EVICT_DATABASE_EVERY_UNIT, EVICT_DATABASE_OLDER_THAN, EVICT_DATABASE_OLDER_THAN_UNIT); @@ -103,9 +103,8 @@ public ComponentIdStore(@NotNull Database database) { * @param evictOlderThanUnit the unit of the 'evictOlderThan' value */ @SuppressWarnings({"WeakerAccess", "ConstructorWithTooManyParameters"}) - public ComponentIdStore(@NotNull Database database, long evictEveryInitialDelay, - long evictEveryDelay, ChronoUnit evictEveryUnit, long evictOlderThan, - @SuppressWarnings("TypeMayBeWeakened") ChronoUnit evictOlderThanUnit) { + public ComponentIdStore(Database database, long evictEveryInitialDelay, long evictEveryDelay, + ChronoUnit evictEveryUnit, long evictOlderThan, ChronoUnit evictOlderThanUnit) { this.database = database; evictDatabaseOlderThan = evictOlderThan; evictDatabaseOlderThanUnit = evictOlderThanUnit; @@ -137,7 +136,7 @@ public ComponentIdStore(@NotNull Database database, long evictEveryInitialDelay, * * @param listener the listener to add */ - public void addComponentIdRemovedListener(@NotNull Consumer listener) { + public void addComponentIdRemovedListener(Consumer listener) { componentIdRemovedListeners.add(listener); } @@ -156,7 +155,8 @@ public void addComponentIdRemovedListener(@NotNull Consumer listene * format and could not be serialized */ @SuppressWarnings("WeakerAccess") - public @NotNull Optional get(@NotNull UUID uuid) { + @Nonnull + public Optional get(UUID uuid) { synchronized (storeLock) { // Get it from the cache or, if not found, the database return Optional.ofNullable(storeCache.getIfPresent(uuid)).or(() -> { @@ -188,8 +188,7 @@ public void addComponentIdRemovedListener(@NotNull Consumer listene * was in an unexpected format and could not be deserialized */ @SuppressWarnings("WeakerAccess") - public void putOrThrow(@NotNull UUID uuid, @NotNull ComponentId componentId, - @NotNull Lifespan lifespan) { + public void putOrThrow(UUID uuid, ComponentId componentId, Lifespan lifespan) { Supplier alreadyExistsMessageSupplier = () -> "The UUID '%s' already exists and is associated to a component id." .formatted(uuid); @@ -218,7 +217,8 @@ public void putOrThrow(@NotNull UUID uuid, @NotNull ComponentId componentId, } } - private @NotNull Optional getFromDatabase(@NotNull UUID uuid) { + @Nonnull + private Optional getFromDatabase(UUID uuid) { return database.read(context -> Optional .ofNullable(context.selectFrom(ComponentIds.COMPONENT_IDS) .where(ComponentIds.COMPONENT_IDS.UUID.eq(uuid.toString())) @@ -235,7 +235,7 @@ public void putOrThrow(@NotNull UUID uuid, @NotNull ComponentId componentId, * @param uuid the uuid to heat * @throws IllegalArgumentException if there is no, or multiple, records associated to that UUID */ - private void heatRecord(@NotNull UUID uuid) { + private void heatRecord(UUID uuid) { int updatedRecords; synchronized (storeLock) { updatedRecords = @@ -294,7 +294,8 @@ private void evictDatabase() { } } - private static @NotNull String serializeComponentId(@NotNull ComponentId componentId) { + @Nonnull + private static String serializeComponentId(ComponentId componentId) { try { return CSV.writerFor(ComponentId.class) .with(CSV.schemaFor(ComponentId.class)) @@ -304,7 +305,8 @@ private void evictDatabase() { } } - private static @NotNull ComponentId deserializeComponentId(@NotNull String componentId) { + @Nonnull + private static ComponentId deserializeComponentId(String componentId) { try { return CSV.readerFor(ComponentId.class) .with(CSV.schemaFor(ComponentId.class)) diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/componentids/package-info.java b/application/src/main/java/org/togetherjava/tjbot/commands/componentids/package-info.java index 239d79060a..e52a9953c4 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/componentids/package-info.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/componentids/package-info.java @@ -2,7 +2,7 @@ * This package provides utilities to generate, persist and parse component IDs. *

    * See - * {@link org.togetherjava.tjbot.commands.SlashCommand#onSlashCommand(net.dv8tion.jda.api.events.interaction.SlashCommandInteractionEvent)} + * {@link org.togetherjava.tjbot.commands.SlashCommand#onSlashCommand(net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent)} * for details on component IDs. *

    * The class {@link org.togetherjava.tjbot.commands.componentids.ComponentIdStore} is the central @@ -10,4 +10,7 @@ * {@link org.togetherjava.tjbot.commands.componentids.ComponentIdGenerator} and * {@link org.togetherjava.tjbot.commands.componentids.ComponentIdParser}. */ +@ParametersAreNonnullByDefault package org.togetherjava.tjbot.commands.componentids; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/filesharing/FileSharingMessageListener.java b/application/src/main/java/org/togetherjava/tjbot/commands/filesharing/FileSharingMessageListener.java index 8aa38da231..2267fe851d 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/filesharing/FileSharingMessageListener.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/filesharing/FileSharingMessageListener.java @@ -8,12 +8,12 @@ import net.dv8tion.jda.api.entities.User; import net.dv8tion.jda.api.events.message.MessageReceivedEvent; import net.dv8tion.jda.api.interactions.components.buttons.Button; -import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.togetherjava.tjbot.commands.MessageReceiverAdapter; import org.togetherjava.tjbot.config.Config; +import javax.annotation.Nonnull; import java.io.IOException; import java.io.InputStream; import java.io.UncheckedIOException; @@ -23,7 +23,10 @@ import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.nio.charset.StandardCharsets; -import java.util.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Predicate; @@ -55,7 +58,7 @@ public class FileSharingMessageListener extends MessageReceiverAdapter { * @param config used to get api key and channel names. * @see org.togetherjava.tjbot.commands.Features */ - public FileSharingMessageListener(@NotNull Config config) { + public FileSharingMessageListener(Config config) { super(Pattern.compile(".*")); gistApiKey = config.getGistApiKey(); @@ -66,7 +69,7 @@ public FileSharingMessageListener(@NotNull Config config) { } @Override - public void onMessageReceived(@NotNull MessageReceivedEvent event) { + public void onMessageReceived(MessageReceivedEvent event) { User author = event.getAuthor(); if (author.isBot() || event.isWebhookMessage()) { return; @@ -98,7 +101,7 @@ public void onMessageReceived(@NotNull MessageReceivedEvent event) { }); } - private boolean isAttachmentRelevant(@NotNull Message.Attachment attachment) { + private boolean isAttachmentRelevant(Message.Attachment attachment) { String extension = attachment.getFileExtension(); if (extension == null) { return false; @@ -107,8 +110,8 @@ private boolean isAttachmentRelevant(@NotNull Message.Attachment attachment) { } - private void processAttachments(@NotNull MessageReceivedEvent event, - @NotNull List attachments) { + private void processAttachments(MessageReceivedEvent event, + List attachments) { Map nameToFile = new ConcurrentHashMap<>(); @@ -130,7 +133,8 @@ private void processAttachments(@NotNull MessageReceivedEvent event, sendResponse(event, url); } - private @NotNull String readAttachment(@NotNull InputStream stream) { + @Nonnull + private String readAttachment(InputStream stream) { try (stream) { return new String(stream.readAllBytes(), StandardCharsets.UTF_8); } catch (IOException e) { @@ -138,7 +142,8 @@ private void processAttachments(@NotNull MessageReceivedEvent event, } } - private @NotNull String getNameOf(@NotNull Message.Attachment attachment) { + @Nonnull + private String getNameOf(Message.Attachment attachment) { String fileName = attachment.getFileName(); String fileExtension = attachment.getFileExtension(); @@ -158,7 +163,8 @@ private void processAttachments(@NotNull MessageReceivedEvent event, return fileName; } - private @NotNull String uploadToGist(@NotNull GistRequest jsonRequest) { + @Nonnull + private String uploadToGist(GistRequest jsonRequest) { String body; try { body = JSON.writeValueAsString(jsonRequest); @@ -204,7 +210,7 @@ private void processAttachments(@NotNull MessageReceivedEvent event, return gistResponse.getHtmlUrl(); } - private void sendResponse(@NotNull MessageReceivedEvent event, @NotNull String url) { + private void sendResponse(MessageReceivedEvent event, String url) { Message message = event.getMessage(); String messageContent = "I uploaded your attachments as **gist**. That way, they are easier to read for everyone, especially mobile users 👍"; @@ -212,7 +218,7 @@ private void sendResponse(@NotNull MessageReceivedEvent event, @NotNull String u message.reply(messageContent).setActionRow(Button.link(url, "gist")).queue(); } - private boolean isHelpThread(@NotNull MessageReceivedEvent event) { + private boolean isHelpThread(MessageReceivedEvent event) { if (event.getChannelType() != ChannelType.GUILD_PUBLIC_THREAD) { return false; } diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/filesharing/GistFile.java b/application/src/main/java/org/togetherjava/tjbot/commands/filesharing/GistFile.java index cba4472b4e..343a7a1730 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/filesharing/GistFile.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/filesharing/GistFile.java @@ -1,10 +1,8 @@ package org.togetherjava.tjbot.commands.filesharing; -import org.jetbrains.annotations.NotNull; - /** * @see Create a Gist via * API */ -record GistFile(@NotNull String content) { +record GistFile(String content) { } diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/filesharing/GistFiles.java b/application/src/main/java/org/togetherjava/tjbot/commands/filesharing/GistFiles.java index 3d3f8ca032..1541a676c7 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/filesharing/GistFiles.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/filesharing/GistFiles.java @@ -1,7 +1,6 @@ package org.togetherjava.tjbot.commands.filesharing; import com.fasterxml.jackson.annotation.JsonAnyGetter; -import org.jetbrains.annotations.NotNull; import java.util.Map; @@ -9,5 +8,5 @@ * @see Create a Gist via * API */ -record GistFiles(@NotNull @JsonAnyGetter Map nameToContent) { +record GistFiles(@JsonAnyGetter Map nameToContent) { } diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/filesharing/GistRequest.java b/application/src/main/java/org/togetherjava/tjbot/commands/filesharing/GistRequest.java index d1e1156547..6b1dd78575 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/filesharing/GistRequest.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/filesharing/GistRequest.java @@ -1,12 +1,10 @@ package org.togetherjava.tjbot.commands.filesharing; import com.fasterxml.jackson.annotation.JsonProperty; -import org.jetbrains.annotations.NotNull; /** * @see Create a Gist via * API */ -record GistRequest(@NotNull String description, @JsonProperty("public") boolean isPublic, - @NotNull GistFiles files) { +record GistRequest(String description, @JsonProperty("public") boolean isPublic, GistFiles files) { } diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/filesharing/GistResponse.java b/application/src/main/java/org/togetherjava/tjbot/commands/filesharing/GistResponse.java index af7d3f2ddf..1d35277fcb 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/filesharing/GistResponse.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/filesharing/GistResponse.java @@ -2,7 +2,8 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; -import org.jetbrains.annotations.NotNull; + +import javax.annotation.Nonnull; /** * @see Create a Gist via @@ -13,11 +14,12 @@ final class GistResponse { @JsonProperty("html_url") private String htmlUrl; - public @NotNull String getHtmlUrl() { - return this.htmlUrl; + @Nonnull + public String getHtmlUrl() { + return htmlUrl; } - public void setHtmlUrl(@NotNull String htmlUrl) { + public void setHtmlUrl(String htmlUrl) { this.htmlUrl = htmlUrl; } } diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/filesharing/package-info.java b/application/src/main/java/org/togetherjava/tjbot/commands/filesharing/package-info.java index 3eba1af69d..8f5f6befeb 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/filesharing/package-info.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/filesharing/package-info.java @@ -2,4 +2,7 @@ * This package offers all the functionality for automatically uploading files to sharing services. * The core class is {@link org.togetherjava.tjbot.commands.filesharing.FileSharingMessageListener}. */ +@ParametersAreNonnullByDefault package org.togetherjava.tjbot.commands.filesharing; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/help/AskCommand.java b/application/src/main/java/org/togetherjava/tjbot/commands/help/AskCommand.java index 70160c207b..16968d2d9b 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/help/AskCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/help/AskCommand.java @@ -9,13 +9,13 @@ import net.dv8tion.jda.api.interactions.commands.build.OptionData; import net.dv8tion.jda.api.requests.ErrorResponse; import net.dv8tion.jda.api.requests.RestAction; -import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.togetherjava.tjbot.commands.SlashCommandAdapter; import org.togetherjava.tjbot.commands.SlashCommandVisibility; import org.togetherjava.tjbot.config.Config; +import javax.annotation.Nonnull; import java.util.Optional; import static org.togetherjava.tjbot.commands.help.HelpSystemHelper.TITLE_COMPACT_LENGTH_MAX; @@ -54,7 +54,7 @@ public final class AskCommand extends SlashCommandAdapter { * @param config the config to use * @param helper the helper to use */ - public AskCommand(@NotNull Config config, @NotNull HelpSystemHelper helper) { + public AskCommand(Config config, HelpSystemHelper helper) { super("ask", "Ask a question - use this in the staging channel", SlashCommandVisibility.GUILD); @@ -72,7 +72,7 @@ public AskCommand(@NotNull Config config, @NotNull HelpSystemHelper helper) { } @Override - public void onSlashCommand(@NotNull SlashCommandInteractionEvent event) { + public void onSlashCommand(SlashCommandInteractionEvent event) { String title = event.getOption(TITLE_OPTION).getAsString(); String category = event.getOption(CATEGORY_OPTION).getAsString(); @@ -102,7 +102,7 @@ public void onSlashCommand(@NotNull SlashCommandInteractionEvent event) { }, e -> handleFailure(e, eventHook)); } - private boolean handleIsValidTitle(@NotNull CharSequence title, @NotNull IReplyCallback event) { + private boolean handleIsValidTitle(CharSequence title, IReplyCallback event) { if (HelpSystemHelper.isTitleValid(title)) { return true; } @@ -119,18 +119,18 @@ private boolean handleIsValidTitle(@NotNull CharSequence title, @NotNull IReplyC return false; } - private @NotNull RestAction handleEvent(@NotNull InteractionHook eventHook, - @NotNull ThreadChannel threadChannel, @NotNull Member author, @NotNull String title, - @NotNull String category, @NotNull Guild guild) { + @Nonnull + private RestAction handleEvent(InteractionHook eventHook, ThreadChannel threadChannel, + Member author, String title, String category, Guild guild) { helper.writeHelpThreadToDatabase(author, threadChannel); return sendInitialMessage(guild, threadChannel, author, title, category) .flatMap(any -> notifyUser(eventHook, threadChannel)) .flatMap(any -> helper.sendExplanationMessage(threadChannel)); } - private RestAction sendInitialMessage(@NotNull Guild guild, - @NotNull ThreadChannel threadChannel, @NotNull Member author, @NotNull String title, - @NotNull String category) { + @Nonnull + private RestAction sendInitialMessage(Guild guild, ThreadChannel threadChannel, + Member author, String title, String category) { String roleMentionDescription = helper.handleFindRoleForCategory(category, guild) .map(role -> " (%s)".formatted(role.getAsMention())) .orElse(""); @@ -148,15 +148,15 @@ private RestAction sendInitialMessage(@NotNull Guild guild, .flatMap(message -> message.editMessage(contentWithRole)); } - private static @NotNull RestAction notifyUser(@NotNull InteractionHook eventHook, - @NotNull IMentionable threadChannel) { + @Nonnull + private static RestAction notifyUser(InteractionHook eventHook, + IMentionable threadChannel) { return eventHook.editOriginal(""" Created a thread for you: %s Please ask your question there, thanks.""".formatted(threadChannel.getAsMention())); } - private static void handleFailure(@NotNull Throwable exception, - @NotNull InteractionHook eventHook) { + private static void handleFailure(Throwable exception, InteractionHook eventHook) { if (exception instanceof ErrorResponseException responseException) { ErrorResponse response = responseException.getErrorResponse(); if (response == ErrorResponse.MAX_CHANNELS diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/help/AutoPruneHelperRoutine.java b/application/src/main/java/org/togetherjava/tjbot/commands/help/AutoPruneHelperRoutine.java index f633972da8..bef93f9889 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/help/AutoPruneHelperRoutine.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/help/AutoPruneHelperRoutine.java @@ -5,7 +5,6 @@ import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Role; import net.dv8tion.jda.api.entities.TextChannel; -import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.togetherjava.tjbot.commands.Routine; @@ -13,6 +12,7 @@ import org.togetherjava.tjbot.db.Database; import org.togetherjava.tjbot.moderation.ModAuditLogWriter; +import javax.annotation.Nonnull; import java.time.Duration; import java.time.Instant; import java.time.Period; @@ -50,8 +50,8 @@ public final class AutoPruneHelperRoutine implements Routine { * @param modAuditLogWriter to inform mods when manual pruning becomes necessary * @param database to determine whether an user is inactive */ - public AutoPruneHelperRoutine(@NotNull Config config, @NotNull HelpSystemHelper helper, - @NotNull ModAuditLogWriter modAuditLogWriter, @NotNull Database database) { + public AutoPruneHelperRoutine(Config config, HelpSystemHelper helper, + ModAuditLogWriter modAuditLogWriter, Database database) { allCategories = config.getHelpSystem().getCategories(); this.helper = helper; this.modAuditLogWriter = modAuditLogWriter; @@ -59,16 +59,17 @@ public AutoPruneHelperRoutine(@NotNull Config config, @NotNull HelpSystemHelper } @Override - public @NotNull Schedule createSchedule() { + @Nonnull + public Schedule createSchedule() { return new Schedule(ScheduleMode.FIXED_RATE, 0, 1, TimeUnit.HOURS); } @Override - public void runRoutine(@NotNull JDA jda) { + public void runRoutine(JDA jda) { jda.getGuildCache().forEach(this::pruneForGuild); } - private void pruneForGuild(@NotNull Guild guild) { + private void pruneForGuild(Guild guild) { TextChannel overviewChannel = guild.getTextChannels() .stream() .filter(channel -> helper.isOverviewChannelName(channel.getName())) @@ -83,8 +84,7 @@ private void pruneForGuild(@NotNull Guild guild) { .forEach(role -> pruneRoleIfFull(role, overviewChannel, now)); } - private void pruneRoleIfFull(@NotNull Role role, @NotNull TextChannel overviewChannel, - @NotNull Instant when) { + private void pruneRoleIfFull(Role role, TextChannel overviewChannel, Instant when) { role.getGuild().findMembersWithRoles(role).onSuccess(members -> { if (isRoleFull(members)) { logger.debug("Helper role {} is full, starting to prune.", role.getName()); @@ -93,12 +93,12 @@ private void pruneRoleIfFull(@NotNull Role role, @NotNull TextChannel overviewCh }); } - private boolean isRoleFull(@NotNull Collection members) { + private boolean isRoleFull(Collection members) { return members.size() >= ROLE_FULL_THRESHOLD; } - private void pruneRole(@NotNull Role role, @NotNull List members, - @NotNull TextChannel overviewChannel, @NotNull Instant when) { + private void pruneRole(Role role, List members, TextChannel overviewChannel, + Instant when) { List membersShuffled = new ArrayList<>(members); Collections.shuffle(membersShuffled); @@ -124,7 +124,7 @@ private void pruneRole(@NotNull Role role, @NotNull List membe membersToPrune.forEach(member -> pruneMemberFromRole(member, role, overviewChannel)); } - private boolean isMemberInactive(@NotNull Member member, @NotNull Instant when) { + private boolean isMemberInactive(Member member, Instant when) { if (member.hasTimeJoined()) { Instant memberJoined = member.getTimeJoined().toInstant(); if (Duration.between(memberJoined, when).toDays() <= RECENTLY_JOINED_DAYS) { @@ -143,8 +143,7 @@ private boolean isMemberInactive(@NotNull Member member, @NotNull Instant when) .and(HELP_CHANNEL_MESSAGES.SENT_AT.greaterThan(latestActiveMoment)))) == 0; } - private void pruneMemberFromRole(@NotNull Member member, @NotNull Role role, - @NotNull TextChannel overviewChannel) { + private void pruneMemberFromRole(Member member, Role role, TextChannel overviewChannel) { Guild guild = member.getGuild(); String dmMessage = @@ -160,7 +159,7 @@ private void pruneMemberFromRole(@NotNull Member member, @NotNull Role role, .queue(); } - private void warnModsAbout(@NotNull String message, @NotNull Guild guild) { + private void warnModsAbout(String message, Guild guild) { logger.warn(message); modAuditLogWriter.write("Auto-prune helpers", message, null, Instant.now(), guild); diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/help/BotMessageCleanup.java b/application/src/main/java/org/togetherjava/tjbot/commands/help/BotMessageCleanup.java index 8584b29430..f066380f27 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/help/BotMessageCleanup.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/help/BotMessageCleanup.java @@ -7,13 +7,13 @@ import net.dv8tion.jda.api.entities.TextChannel; import net.dv8tion.jda.api.requests.RestAction; import net.dv8tion.jda.internal.requests.CompletedRestAction; -import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.togetherjava.tjbot.commands.Routine; import org.togetherjava.tjbot.config.Config; import org.togetherjava.tjbot.config.HelpSystemConfig; +import javax.annotation.Nonnull; import java.time.Duration; import java.time.Instant; import java.time.OffsetDateTime; @@ -46,7 +46,7 @@ public final class BotMessageCleanup implements Routine { * * @param config the config to use */ - public BotMessageCleanup(@NotNull Config config) { + public BotMessageCleanup(Config config) { this.config = config.getHelpSystem(); isStagingChannelName = Pattern.compile(config.getHelpSystem().getStagingChannelPattern()) @@ -54,16 +54,17 @@ public BotMessageCleanup(@NotNull Config config) { } @Override - public @NotNull Schedule createSchedule() { + @Nonnull + public Schedule createSchedule() { return new Schedule(ScheduleMode.FIXED_RATE, 1, 1, TimeUnit.MINUTES); } @Override - public void runRoutine(@NotNull JDA jda) { + public void runRoutine(JDA jda) { jda.getGuildCache().forEach(this::cleanupBotMessagesForGuild); } - private void cleanupBotMessagesForGuild(@NotNull Guild guild) { + private void cleanupBotMessagesForGuild(Guild guild) { Optional maybeStagingChannel = handleRequireStagingChannel(guild); if (maybeStagingChannel.isEmpty()) { @@ -78,7 +79,8 @@ private void cleanupBotMessagesForGuild(@NotNull Guild guild) { .queue(); } - private @NotNull Optional handleRequireStagingChannel(@NotNull Guild guild) { + @Nonnull + private Optional handleRequireStagingChannel(Guild guild) { Optional maybeChannel = guild.getTextChannelCache() .stream() .filter(channel -> isStagingChannelName.test(channel.getName())) @@ -94,7 +96,7 @@ private void cleanupBotMessagesForGuild(@NotNull Guild guild) { return maybeChannel; } - private static boolean shouldMessageBeCleanedUp(@NotNull Message message) { + private static boolean shouldMessageBeCleanedUp(Message message) { if (!message.getAuthor().isBot()) { return false; } @@ -106,8 +108,9 @@ private static boolean shouldMessageBeCleanedUp(@NotNull Message message) { return deleteWhen.isBefore(Instant.now()); } - private static @NotNull RestAction cleanupBotMessages( - @NotNull GuildMessageChannel channel, @NotNull Collection messages) { + @Nonnull + private static RestAction cleanupBotMessages(GuildMessageChannel channel, + Collection messages) { logger.debug("Cleaning up old bot messages in the staging channel"); List messageIdsToDelete = messages.stream() .filter(BotMessageCleanup::shouldMessageBeCleanedUp) diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/help/ChangeHelpCategoryCommand.java b/application/src/main/java/org/togetherjava/tjbot/commands/help/ChangeHelpCategoryCommand.java index 9bf24e54f0..1f84add910 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/help/ChangeHelpCategoryCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/help/ChangeHelpCategoryCommand.java @@ -11,11 +11,11 @@ import net.dv8tion.jda.api.interactions.commands.OptionType; import net.dv8tion.jda.api.interactions.commands.build.OptionData; import net.dv8tion.jda.api.requests.RestAction; -import org.jetbrains.annotations.NotNull; import org.togetherjava.tjbot.commands.SlashCommandAdapter; import org.togetherjava.tjbot.commands.SlashCommandVisibility; import org.togetherjava.tjbot.config.Config; +import javax.annotation.Nonnull; import java.time.Instant; import java.time.temporal.ChronoUnit; import java.util.Locale; @@ -47,7 +47,7 @@ public final class ChangeHelpCategoryCommand extends SlashCommandAdapter { * @param config the config to use * @param helper the helper to use */ - public ChangeHelpCategoryCommand(@NotNull Config config, @NotNull HelpSystemHelper helper) { + public ChangeHelpCategoryCommand(Config config, HelpSystemHelper helper) { super("change-help-category", "changes the category of a help thread", SlashCommandVisibility.GUILD); @@ -68,7 +68,7 @@ public ChangeHelpCategoryCommand(@NotNull Config config, @NotNull HelpSystemHelp } @Override - public void onSlashCommand(@NotNull SlashCommandInteractionEvent event) { + public void onSlashCommand(SlashCommandInteractionEvent event) { String category = event.getOption(CATEGORY_OPTION).getAsString(); ThreadChannel helpThread = event.getThreadChannel(); @@ -96,9 +96,9 @@ public void onSlashCommand(@NotNull SlashCommandInteractionEvent event) { .queue(); } - private @NotNull RestAction sendCategoryChangedMessage(@NotNull Guild guild, - @NotNull InteractionHook hook, @NotNull ThreadChannel helpThread, - @NotNull String category) { + @Nonnull + private RestAction sendCategoryChangedMessage(Guild guild, InteractionHook hook, + ThreadChannel helpThread, String category) { String changedContent = "Changed the category to **%s**.".formatted(category); var action = hook.editOriginal(changedContent); @@ -119,7 +119,7 @@ public void onSlashCommand(@NotNull SlashCommandInteractionEvent event) { .flatMap(message -> message.editMessage(headsUpWithRole))); } - private boolean isHelpThreadOnCooldown(@NotNull ThreadChannel helpThread) { + private boolean isHelpThreadOnCooldown(ThreadChannel helpThread) { return Optional .ofNullable(helpThreadIdToLastCategoryChange.getIfPresent(helpThread.getIdLong())) .map(lastCategoryChange -> lastCategoryChange.plus(COOLDOWN_DURATION_VALUE, diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/help/ChangeHelpTitleCommand.java b/application/src/main/java/org/togetherjava/tjbot/commands/help/ChangeHelpTitleCommand.java index 5090f26d07..8ea5288f25 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/help/ChangeHelpTitleCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/help/ChangeHelpTitleCommand.java @@ -6,7 +6,6 @@ import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import net.dv8tion.jda.api.interactions.callbacks.IReplyCallback; import net.dv8tion.jda.api.interactions.commands.OptionType; -import org.jetbrains.annotations.NotNull; import org.togetherjava.tjbot.commands.SlashCommandAdapter; import org.togetherjava.tjbot.commands.SlashCommandVisibility; @@ -40,7 +39,7 @@ public final class ChangeHelpTitleCommand extends SlashCommandAdapter { * * @param helper the helper to use */ - public ChangeHelpTitleCommand(@NotNull HelpSystemHelper helper) { + public ChangeHelpTitleCommand(HelpSystemHelper helper) { super("change-help-title", "changes the title of a help thread", SlashCommandVisibility.GUILD); @@ -55,7 +54,7 @@ public ChangeHelpTitleCommand(@NotNull HelpSystemHelper helper) { } @Override - public void onSlashCommand(@NotNull SlashCommandInteractionEvent event) { + public void onSlashCommand(SlashCommandInteractionEvent event) { String title = event.getOption(TITLE_OPTION).getAsString(); if (!handleIsValidTitle(title, event)) { @@ -84,7 +83,7 @@ public void onSlashCommand(@NotNull SlashCommandInteractionEvent event) { .queue(); } - private boolean isHelpThreadOnCooldown(@NotNull ThreadChannel helpThread) { + private boolean isHelpThreadOnCooldown(ThreadChannel helpThread) { return Optional .ofNullable(helpThreadIdToLastTitleChange.getIfPresent(helpThread.getIdLong())) .map(lastCategoryChange -> lastCategoryChange.plus(COOLDOWN_DURATION_VALUE, @@ -93,7 +92,7 @@ private boolean isHelpThreadOnCooldown(@NotNull ThreadChannel helpThread) { .isPresent(); } - private boolean handleIsValidTitle(@NotNull CharSequence title, @NotNull IReplyCallback event) { + private boolean handleIsValidTitle(CharSequence title, IReplyCallback event) { if (HelpSystemHelper.isTitleValid(title)) { return true; } diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/help/CloseCommand.java b/application/src/main/java/org/togetherjava/tjbot/commands/help/CloseCommand.java index 437e75131c..3b0e348e7e 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/help/CloseCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/help/CloseCommand.java @@ -6,7 +6,6 @@ import net.dv8tion.jda.api.entities.MessageEmbed; import net.dv8tion.jda.api.entities.ThreadChannel; import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; -import org.jetbrains.annotations.NotNull; import org.togetherjava.tjbot.commands.SlashCommandAdapter; import org.togetherjava.tjbot.commands.SlashCommandVisibility; @@ -41,7 +40,7 @@ public CloseCommand() { } @Override - public void onSlashCommand(@NotNull SlashCommandInteractionEvent event) { + public void onSlashCommand(SlashCommandInteractionEvent event) { ThreadChannel helpThread = event.getThreadChannel(); if (helpThread.isArchived()) { event.reply("This thread is already closed.").setEphemeral(true).queue(); @@ -66,7 +65,7 @@ public void onSlashCommand(@NotNull SlashCommandInteractionEvent event) { event.replyEmbeds(embed).flatMap(any -> helpThread.getManager().setArchived(true)).queue(); } - private boolean isHelpThreadOnCooldown(@NotNull ThreadChannel helpThread) { + private boolean isHelpThreadOnCooldown(ThreadChannel helpThread) { return Optional.ofNullable(helpThreadIdToLastClose.getIfPresent(helpThread.getIdLong())) .map(lastCategoryChange -> lastCategoryChange.plus(COOLDOWN_DURATION_VALUE, COOLDOWN_DURATION_UNIT)) diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/help/HelpSystemHelper.java b/application/src/main/java/org/togetherjava/tjbot/commands/help/HelpSystemHelper.java index b30c1a56d9..e8500af163 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/help/HelpSystemHelper.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/help/HelpSystemHelper.java @@ -5,8 +5,6 @@ import net.dv8tion.jda.api.requests.RestAction; import net.dv8tion.jda.api.requests.restaction.MessageAction; import net.dv8tion.jda.internal.requests.CompletedRestAction; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.togetherjava.tjbot.config.Config; @@ -15,6 +13,8 @@ import org.togetherjava.tjbot.db.generated.tables.HelpThreads; import org.togetherjava.tjbot.db.generated.tables.records.HelpThreadsRecord; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; import java.awt.Color; import java.io.InputStream; import java.util.List; @@ -60,7 +60,7 @@ public final class HelpSystemHelper { * @param config the config to use * @param database the database to store help thread metadata in */ - public HelpSystemHelper(@NotNull Config config, @NotNull Database database) { + public HelpSystemHelper(Config config, Database database) { HelpSystemConfig helpConfig = config.getHelpSystem(); this.database = database; @@ -73,7 +73,8 @@ public HelpSystemHelper(@NotNull Config config, @NotNull Database database) { categoryRoleSuffix = helpConfig.getCategoryRoleSuffix(); } - RestAction sendExplanationMessage(@NotNull MessageChannel threadChannel) { + @Nonnull + RestAction sendExplanationMessage(MessageChannel threadChannel) { boolean useCodeSyntaxExampleImage = true; InputStream codeSyntaxExampleData = AskCommand.class.getResourceAsStream("/" + CODE_SYNTAX_EXAMPLE_PATH); @@ -119,20 +120,21 @@ void writeHelpThreadToDatabase(Member author, ThreadChannel threadChannel) { }); } - private static @NotNull MessageEmbed embedWith(@NotNull CharSequence message) { + @Nonnull + private static MessageEmbed embedWith(CharSequence message) { return embedWith(message, null); } - private static @NotNull MessageEmbed embedWith(@NotNull CharSequence message, - @Nullable String imageUrl) { + @Nonnull + private static MessageEmbed embedWith(CharSequence message, @Nullable String imageUrl) { return new EmbedBuilder().setColor(AMBIENT_COLOR) .setDescription(message) .setImage(imageUrl) .build(); } - @NotNull - Optional handleFindRoleForCategory(@NotNull String category, @NotNull Guild guild) { + @Nonnull + Optional handleFindRoleForCategory(String category, Guild guild) { String roleName = category + categoryRoleSuffix; Optional maybeHelperRole = guild.getRolesByName(roleName, true).stream().findAny(); @@ -143,14 +145,13 @@ Optional handleFindRoleForCategory(@NotNull String category, @NotNull Guil return maybeHelperRole; } - @NotNull - Optional getCategoryOfChannel(@NotNull Channel channel) { + @Nonnull + Optional getCategoryOfChannel(Channel channel) { return Optional.ofNullable(HelpThreadName.ofChannelName(channel.getName()).category); } - @NotNull - RestAction renameChannelToCategory(@NotNull GuildChannel channel, - @NotNull String category) { + @Nonnull + RestAction renameChannelToCategory(GuildChannel channel, String category) { HelpThreadName currentName = HelpThreadName.ofChannelName(channel.getName()); HelpThreadName nextName = new HelpThreadName(currentName.activity, category, currentName.title); @@ -158,8 +159,8 @@ RestAction renameChannelToCategory(@NotNull GuildChannel channel, return renameChannel(channel, currentName, nextName); } - @NotNull - RestAction renameChannelToTitle(@NotNull GuildChannel channel, @NotNull String title) { + @Nonnull + RestAction renameChannelToTitle(GuildChannel channel, String title) { HelpThreadName currentName = HelpThreadName.ofChannelName(channel.getName()); HelpThreadName nextName = new HelpThreadName(currentName.activity, currentName.category, title); @@ -167,9 +168,8 @@ RestAction renameChannelToTitle(@NotNull GuildChannel channel, @NotNull St return renameChannel(channel, currentName, nextName); } - @NotNull - RestAction renameChannelToActivity(@NotNull GuildChannel channel, - @NotNull ThreadActivity activity) { + @Nonnull + RestAction renameChannelToActivity(GuildChannel channel, ThreadActivity activity) { HelpThreadName currentName = HelpThreadName.ofChannelName(channel.getName()); HelpThreadName nextName = new HelpThreadName(activity, currentName.category, currentName.title); @@ -177,9 +177,9 @@ RestAction renameChannelToActivity(@NotNull GuildChannel channel, return renameChannel(channel, currentName, nextName); } - @NotNull - private RestAction renameChannel(@NotNull GuildChannel channel, - @NotNull HelpThreadName currentName, @NotNull HelpThreadName nextName) { + @Nonnull + private RestAction renameChannel(GuildChannel channel, HelpThreadName currentName, + HelpThreadName nextName) { if (currentName.equals(nextName)) { // Do not stress rate limits if no actual change is done return new CompletedRestAction<>(channel.getJDA(), null); @@ -188,25 +188,25 @@ private RestAction renameChannel(@NotNull GuildChannel channel, return channel.getManager().setName(nextName.toChannelName()); } - boolean isOverviewChannelName(@NotNull String channelName) { + boolean isOverviewChannelName(String channelName) { return isOverviewChannelName.test(channelName); } - @NotNull + @Nonnull String getOverviewChannelPattern() { return overviewChannelPattern; } - boolean isStagingChannelName(@NotNull String channelName) { + boolean isStagingChannelName(String channelName) { return isStagingChannelName.test(channelName); } - @NotNull + @Nonnull String getStagingChannelPattern() { return stagingChannelPattern; } - static boolean isTitleValid(@NotNull CharSequence title) { + static boolean isTitleValid(CharSequence title) { String titleCompact = TITLE_COMPACT_REMOVAL_PATTERN.matcher(title).replaceAll(""); return titleCompact.length() >= TITLE_COMPACT_LENGTH_MIN @@ -214,9 +214,9 @@ static boolean isTitleValid(@NotNull CharSequence title) { && !titleCompact.toLowerCase(Locale.US).contains("help"); } - @NotNull - Optional handleRequireOverviewChannel(@NotNull Guild guild, - @NotNull Consumer consumeChannelPatternIfNotFound) { + @Nonnull + Optional handleRequireOverviewChannel(Guild guild, + Consumer consumeChannelPatternIfNotFound) { Predicate isChannelName = this::isOverviewChannelName; String channelPattern = getOverviewChannelPattern(); @@ -232,9 +232,9 @@ Optional handleRequireOverviewChannel(@NotNull Guild guild, return maybeChannel; } - @NotNull - Optional handleRequireOverviewChannelForAsk(@NotNull Guild guild, - @NotNull MessageChannel respondTo) { + @Nonnull + Optional handleRequireOverviewChannelForAsk(Guild guild, + MessageChannel respondTo) { return handleRequireOverviewChannel(guild, channelPattern -> { logger.warn( "Attempted to create a help thread, did not find the overview channel matching the configured pattern '{}' for guild '{}'", @@ -246,8 +246,8 @@ Optional handleRequireOverviewChannelForAsk(@NotNull Guild guild, }); } - @NotNull - List getActiveThreadsIn(@NotNull TextChannel channel) { + @Nonnull + List getActiveThreadsIn(TextChannel channel) { return channel.getThreadChannels() .stream() .filter(Predicate.not(ThreadChannel::isArchived)) @@ -255,8 +255,9 @@ List getActiveThreadsIn(@NotNull TextChannel channel) { } record HelpThreadName(@Nullable ThreadActivity activity, @Nullable String category, - @NotNull String title) { - static @NotNull HelpThreadName ofChannelName(@NotNull CharSequence channelName) { + String title) { + @Nonnull + static HelpThreadName ofChannelName(CharSequence channelName) { Matcher matcher = EXTRACT_HELP_NAME_PATTERN.matcher(channelName); if (!matcher.matches()) { @@ -273,7 +274,7 @@ record HelpThreadName(@Nullable ThreadActivity activity, @Nullable String catego return new HelpThreadName(activity, category, title); } - @NotNull + @Nonnull String toChannelName() { String activityText = activity == null ? "" : activity.getSymbol() + " "; String categoryText = category == null ? "" : "[%s] ".formatted(category); @@ -289,15 +290,17 @@ enum ThreadActivity { private final String symbol; - ThreadActivity(@NotNull String symbol) { + ThreadActivity(String symbol) { this.symbol = symbol; } - public @NotNull String getSymbol() { + @Nonnull + public String getSymbol() { return symbol; } - static @NotNull ThreadActivity ofSymbol(@NotNull String symbol) { + @Nonnull + static ThreadActivity ofSymbol(String symbol) { return Stream.of(values()) .filter(activity -> activity.getSymbol().equals(symbol)) .findAny() diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/help/HelpThreadActivityUpdater.java b/application/src/main/java/org/togetherjava/tjbot/commands/help/HelpThreadActivityUpdater.java index f21cad798b..9cf9a42c30 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/help/HelpThreadActivityUpdater.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/help/HelpThreadActivityUpdater.java @@ -3,11 +3,11 @@ import net.dv8tion.jda.api.JDA; import net.dv8tion.jda.api.entities.*; import net.dv8tion.jda.api.requests.RestAction; -import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.togetherjava.tjbot.commands.Routine; +import javax.annotation.Nonnull; import java.util.List; import java.util.Map; import java.util.Optional; @@ -33,21 +33,22 @@ public final class HelpThreadActivityUpdater implements Routine { * * @param helper the helper to use */ - public HelpThreadActivityUpdater(@NotNull HelpSystemHelper helper) { + public HelpThreadActivityUpdater(HelpSystemHelper helper) { this.helper = helper; } @Override - public @NotNull Schedule createSchedule() { + @Nonnull + public Schedule createSchedule() { return new Schedule(ScheduleMode.FIXED_RATE, 1, SCHEDULE_MINUTES, TimeUnit.MINUTES); } @Override - public void runRoutine(@NotNull JDA jda) { + public void runRoutine(JDA jda) { jda.getGuildCache().forEach(this::updateActivityForGuild); } - private void updateActivityForGuild(@NotNull Guild guild) { + private void updateActivityForGuild(Guild guild) { Optional maybeOverviewChannel = helper .handleRequireOverviewChannel(guild, channelPattern -> logger.warn( "Unable to update help thread overview, did not find an overview channel matching the configured pattern '{}' for guild '{}'", @@ -66,14 +67,15 @@ private void updateActivityForGuild(@NotNull Guild guild) { activeThreads.forEach(this::updateActivityForThread); } - private void updateActivityForThread(@NotNull ThreadChannel threadChannel) { + private void updateActivityForThread(ThreadChannel threadChannel) { determineActivity(threadChannel) .flatMap( threadActivity -> helper.renameChannelToActivity(threadChannel, threadActivity)) .queue(); } - private static @NotNull RestAction determineActivity( + @Nonnull + private static RestAction determineActivity( MessageChannel channel) { return channel.getHistory().retrievePast(ACTIVITY_DETERMINE_MESSAGE_LIMIT).map(messages -> { if (messages.size() >= ACTIVITY_DETERMINE_MESSAGE_LIMIT) { @@ -94,7 +96,7 @@ private void updateActivityForThread(@NotNull ThreadChannel threadChannel) { }); } - private static boolean isBotMessage(@NotNull Message message) { + private static boolean isBotMessage(Message message) { return message.getAuthor().equals(message.getJDA().getSelfUser()); } } diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/help/HelpThreadAutoArchiver.java b/application/src/main/java/org/togetherjava/tjbot/commands/help/HelpThreadAutoArchiver.java index 00b093956c..b8f3689666 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/help/HelpThreadAutoArchiver.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/help/HelpThreadAutoArchiver.java @@ -4,11 +4,11 @@ import net.dv8tion.jda.api.JDA; import net.dv8tion.jda.api.entities.*; import net.dv8tion.jda.api.utils.TimeUtil; -import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.togetherjava.tjbot.commands.Routine; +import javax.annotation.Nonnull; import java.time.Duration; import java.time.Instant; import java.util.List; @@ -31,21 +31,22 @@ public final class HelpThreadAutoArchiver implements Routine { * * @param helper the helper to use */ - public HelpThreadAutoArchiver(@NotNull HelpSystemHelper helper) { + public HelpThreadAutoArchiver(HelpSystemHelper helper) { this.helper = helper; } @Override - public @NotNull Schedule createSchedule() { + @Nonnull + public Schedule createSchedule() { return new Schedule(ScheduleMode.FIXED_RATE, 0, SCHEDULE_MINUTES, TimeUnit.MINUTES); } @Override - public void runRoutine(@NotNull JDA jda) { + public void runRoutine(JDA jda) { jda.getGuildCache().forEach(this::autoArchiveForGuild); } - private void autoArchiveForGuild(@NotNull Guild guild) { + private void autoArchiveForGuild(Guild guild) { Optional maybeOverviewChannel = helper .handleRequireOverviewChannel(guild, channelPattern -> logger.warn( "Unable to auto archive help threads, did not find an overview channel matching the configured pattern '{}' for guild '{}'", @@ -66,12 +67,12 @@ private void autoArchiveForGuild(@NotNull Guild guild) { .forEach(activeThread -> autoArchiveForThread(activeThread, archiveAfterMoment)); } - private @NotNull Instant computeArchiveAfterMoment() { + @Nonnull + private Instant computeArchiveAfterMoment() { return Instant.now().minus(ARCHIVE_AFTER_INACTIVITY_OF); } - private void autoArchiveForThread(@NotNull ThreadChannel threadChannel, - @NotNull Instant archiveAfterMoment) { + private void autoArchiveForThread(ThreadChannel threadChannel, Instant archiveAfterMoment) { if (shouldBeArchived(threadChannel, archiveAfterMoment)) { logger.debug("Auto archiving help thread {}", threadChannel.getId()); @@ -90,8 +91,7 @@ private void autoArchiveForThread(@NotNull ThreadChannel threadChannel, } } - private static boolean shouldBeArchived(MessageChannel channel, - @NotNull Instant archiveAfterMoment) { + private static boolean shouldBeArchived(MessageChannel channel, Instant archiveAfterMoment) { Instant lastActivity = TimeUtil.getTimeCreated(channel.getLatestMessageIdLong()).toInstant(); diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/help/HelpThreadMetadataPurger.java b/application/src/main/java/org/togetherjava/tjbot/commands/help/HelpThreadMetadataPurger.java index 2fa502b76b..4393b837be 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/help/HelpThreadMetadataPurger.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/help/HelpThreadMetadataPurger.java @@ -1,12 +1,13 @@ package org.togetherjava.tjbot.commands.help; import net.dv8tion.jda.api.JDA; -import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.togetherjava.tjbot.commands.Routine; import org.togetherjava.tjbot.db.Database; import org.togetherjava.tjbot.db.generated.tables.HelpThreads; + +import javax.annotation.Nonnull; import java.time.Instant; import java.time.Period; import java.util.concurrent.TimeUnit; @@ -24,17 +25,18 @@ public class HelpThreadMetadataPurger implements Routine { * * @param database the database used to purge help thread metadata */ - public HelpThreadMetadataPurger(@NotNull Database database) { + public HelpThreadMetadataPurger(Database database) { this.database = database; } @Override - public @NotNull Schedule createSchedule() { + @Nonnull + public Schedule createSchedule() { return new Schedule(ScheduleMode.FIXED_RATE, 0, 4, TimeUnit.HOURS); } @Override - public void runRoutine(@NotNull JDA jda) { + public void runRoutine(JDA jda) { int recordsDeleted = database.writeAndProvide(content -> content.deleteFrom(HelpThreads.HELP_THREADS)) .where(HelpThreads.HELP_THREADS.CREATED_AT diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/help/HelpThreadOverviewUpdater.java b/application/src/main/java/org/togetherjava/tjbot/commands/help/HelpThreadOverviewUpdater.java index 89eafc0292..0905c4991b 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/help/HelpThreadOverviewUpdater.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/help/HelpThreadOverviewUpdater.java @@ -6,14 +6,14 @@ import net.dv8tion.jda.api.events.message.MessageReceivedEvent; import net.dv8tion.jda.api.requests.RestAction; import net.dv8tion.jda.internal.requests.CompletedRestAction; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.togetherjava.tjbot.commands.MessageReceiverAdapter; import org.togetherjava.tjbot.commands.Routine; import org.togetherjava.tjbot.config.Config; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; import java.util.*; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; @@ -51,7 +51,7 @@ public final class HelpThreadOverviewUpdater extends MessageReceiverAdapter impl * @param config the config to use * @param helper the helper to use */ - public HelpThreadOverviewUpdater(@NotNull Config config, @NotNull HelpSystemHelper helper) { + public HelpThreadOverviewUpdater(Config config, HelpSystemHelper helper) { super(Pattern.compile(config.getHelpSystem().getOverviewChannelPattern())); allCategories = config.getHelpSystem().getCategories(); @@ -59,17 +59,18 @@ public HelpThreadOverviewUpdater(@NotNull Config config, @NotNull HelpSystemHelp } @Override - public @NotNull Schedule createSchedule() { + @Nonnull + public Schedule createSchedule() { return new Schedule(ScheduleMode.FIXED_RATE, 1, 1, TimeUnit.MINUTES); } @Override - public void runRoutine(@NotNull JDA jda) { + public void runRoutine(JDA jda) { jda.getGuildCache().forEach(this::updateOverviewForGuild); } @Override - public void onMessageReceived(@NotNull MessageReceivedEvent event) { + public void onMessageReceived(MessageReceivedEvent event) { // Update whenever a thread was created Message message = event.getMessage(); if (message.getType() != MessageType.THREAD_CREATED) { @@ -94,7 +95,7 @@ public void onMessageReceived(@NotNull MessageReceivedEvent event) { UPDATE_SERVICE.schedule(updateOverviewCommand, 2, TimeUnit.SECONDS); } - private void updateOverviewForGuild(@NotNull Guild guild) { + private void updateOverviewForGuild(Guild guild) { Optional maybeOverviewChannel = helper .handleRequireOverviewChannel(guild, channelPattern -> logger.warn( "Unable to update help thread overview, did not find an overview channel matching the configured pattern '{}' for guild '{}'", @@ -107,7 +108,7 @@ private void updateOverviewForGuild(@NotNull Guild guild) { updateOverview(maybeOverviewChannel.orElseThrow()); } - private void updateOverview(@NotNull TextChannel overviewChannel) { + private void updateOverview(TextChannel overviewChannel) { logger.debug("Updating overview of active questions"); List activeThreads = helper.getActiveThreadsIn(overviewChannel); @@ -123,7 +124,8 @@ private void updateOverview(@NotNull TextChannel overviewChannel) { .queue(); } - private @NotNull String createDescription(@NotNull Collection activeThreads) { + @Nonnull + private String createDescription(Collection activeThreads) { if (activeThreads.isEmpty()) { return "Currently none."; } @@ -148,8 +150,8 @@ private void updateOverview(@NotNull TextChannel overviewChannel) { .collect(Collectors.joining("\n\n")); } - private static @NotNull RestAction> getStatusMessage( - @NotNull MessageChannel channel) { + @Nonnull + private static RestAction> getStatusMessage(MessageChannel channel) { return channel.getHistory() .retrievePast(1) .map(messages -> messages.stream() @@ -157,7 +159,7 @@ private void updateOverview(@NotNull TextChannel overviewChannel) { .filter(HelpThreadOverviewUpdater::isStatusMessage)); } - private static boolean isStatusMessage(@NotNull Message message) { + private static boolean isStatusMessage(Message message) { if (!message.getAuthor().equals(message.getJDA().getSelfUser())) { return false; } @@ -166,8 +168,9 @@ private static boolean isStatusMessage(@NotNull Message message) { return content.startsWith(STATUS_TITLE); } - private @NotNull RestAction sendUpdatedOverview(@Nullable Message statusMessage, - @NotNull Message updatedStatusMessage, @NotNull MessageChannel overviewChannel) { + @Nonnull + private RestAction sendUpdatedOverview(@Nullable Message statusMessage, + Message updatedStatusMessage, MessageChannel overviewChannel) { logger.debug("Sending the updated question overview"); if (statusMessage == null) { int currentFailures = FIND_STATUS_MESSAGE_CONSECUTIVE_FAILURES.incrementAndGet(); @@ -190,8 +193,7 @@ private static boolean isStatusMessage(@NotNull Message message) { return overviewChannel.editMessageById(statusMessageId, updatedStatusMessage); } - private record CategoryWithThreads(@NotNull String category, - @NotNull List threads) { + private record CategoryWithThreads(String category, List threads) { String toDiscordString() { String threadListText = threads.stream() @@ -201,8 +203,9 @@ String toDiscordString() { return "**%s**:%n%s".formatted(category, threadListText); } - static @NotNull CategoryWithThreads ofEntry( - Map.@NotNull Entry> categoryAndThreads) { + @Nonnull + static CategoryWithThreads ofEntry( + Map.Entry> categoryAndThreads) { return new CategoryWithThreads(categoryAndThreads.getKey(), categoryAndThreads.getValue()); } diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/help/ImplicitAskListener.java b/application/src/main/java/org/togetherjava/tjbot/commands/help/ImplicitAskListener.java index 22a241fbe3..3a9c37452d 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/help/ImplicitAskListener.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/help/ImplicitAskListener.java @@ -10,12 +10,12 @@ import net.dv8tion.jda.api.requests.ErrorResponse; import net.dv8tion.jda.api.requests.RestAction; import net.dv8tion.jda.api.requests.restaction.MessageAction; -import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.togetherjava.tjbot.commands.MessageReceiverAdapter; import org.togetherjava.tjbot.config.Config; +import javax.annotation.Nonnull; import java.time.Instant; import java.time.temporal.ChronoUnit; import java.util.Optional; @@ -58,7 +58,7 @@ public final class ImplicitAskListener extends MessageReceiverAdapter { * @param config the config to use * @param helper the helper to use */ - public ImplicitAskListener(@NotNull Config config, @NotNull HelpSystemHelper helper) { + public ImplicitAskListener(Config config, HelpSystemHelper helper) { super(Pattern.compile(config.getHelpSystem().getStagingChannelPattern())); userIdToLastHelpThread = Caffeine.newBuilder() @@ -70,7 +70,7 @@ public ImplicitAskListener(@NotNull Config config, @NotNull HelpSystemHelper hel } @Override - public void onMessageReceived(@NotNull MessageReceivedEvent event) { + public void onMessageReceived(MessageReceivedEvent event) { // Only listen to regular messages from users if (event.isWebhookMessage() || event.getMessage().getType() != MessageType.DEFAULT || event.getAuthor().isBot()) { @@ -101,7 +101,7 @@ public void onMessageReceived(@NotNull MessageReceivedEvent event) { }, ImplicitAskListener::handleFailure); } - private boolean handleIsNotOnCooldown(@NotNull Message message) { + private boolean handleIsNotOnCooldown(Message message) { Member author = message.getMember(); Optional maybeLastHelpThread = @@ -125,6 +125,7 @@ private boolean handleIsNotOnCooldown(@NotNull Message message) { return false; } + @Nonnull private Optional getLastHelpThreadIfOnCooldown(long userId) { return Optional.ofNullable(userIdToLastHelpThread.getIfPresent(userId)) .filter(lastHelpThread -> { @@ -136,7 +137,8 @@ private Optional getLastHelpThreadIfOnCooldown(long userId) { }); } - private static @NotNull String createTitle(@NotNull String message) { + @Nonnull + private static String createTitle(String message) { String titleCandidate; if (message.length() < TITLE_MAX_LENGTH) { titleCandidate = message; @@ -154,8 +156,8 @@ private Optional getLastHelpThreadIfOnCooldown(long userId) { return HelpSystemHelper.isTitleValid(titleCandidate) ? titleCandidate : "Untitled"; } - private @NotNull RestAction handleEvent(@NotNull ThreadChannel threadChannel, - @NotNull Message message, @NotNull String title) { + @Nonnull + private RestAction handleEvent(ThreadChannel threadChannel, Message message, String title) { Member author = message.getMember(); helper.writeHelpThreadToDatabase(author, threadChannel); userIdToLastHelpThread.put(author.getIdLong(), @@ -167,13 +169,15 @@ private Optional getLastHelpThreadIfOnCooldown(long userId) { .flatMap(any -> helper.sendExplanationMessage(threadChannel)); } - private static @NotNull RestAction inviteUsersToThread( - @NotNull ThreadChannel threadChannel, @NotNull Member author) { + @Nonnull + private static RestAction inviteUsersToThread(ThreadChannel threadChannel, + Member author) { return threadChannel.addThreadMember(author); } - private static @NotNull MessageAction sendInitialMessage(@NotNull ThreadChannel threadChannel, - @NotNull Message originalMessage, @NotNull String title) { + @Nonnull + private static MessageAction sendInitialMessage(ThreadChannel threadChannel, + Message originalMessage, String title) { String content = originalMessage.getContentRaw(); Member author = originalMessage.getMember(); @@ -194,8 +198,8 @@ private Optional getLastHelpThreadIfOnCooldown(long userId) { return threadChannel.sendMessage(threadMessage); } - private static @NotNull MessageAction notifyUser(@NotNull IMentionable threadChannel, - @NotNull Message message) { + @Nonnull + private static MessageAction notifyUser(IMentionable threadChannel, Message message) { return message.getChannel() .sendMessage( """ @@ -205,7 +209,7 @@ private Optional getLastHelpThreadIfOnCooldown(long userId) { threadChannel.getAsMention())); } - private static void handleFailure(@NotNull Throwable exception) { + private static void handleFailure(Throwable exception) { if (exception instanceof ErrorResponseException responseException) { ErrorResponse response = responseException.getErrorResponse(); if (response == ErrorResponse.MAX_CHANNELS @@ -217,6 +221,6 @@ private static void handleFailure(@NotNull Throwable exception) { logger.error("Attempted to create a help thread, but failed", exception); } - private record HelpThread(long channelId, long authorId, @NotNull Instant creationTime) { + private record HelpThread(long channelId, long authorId, Instant creationTime) { } } diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/help/OnGuildLeaveCloseThreadListener.java b/application/src/main/java/org/togetherjava/tjbot/commands/help/OnGuildLeaveCloseThreadListener.java index 4d8a945f5d..3da99a69e7 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/help/OnGuildLeaveCloseThreadListener.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/help/OnGuildLeaveCloseThreadListener.java @@ -5,15 +5,14 @@ import net.dv8tion.jda.api.entities.ThreadChannel; import net.dv8tion.jda.api.events.guild.member.GuildMemberRemoveEvent; import net.dv8tion.jda.api.hooks.ListenerAdapter; -import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.togetherjava.tjbot.commands.EventReceiver; import org.togetherjava.tjbot.db.Database; import org.togetherjava.tjbot.db.generated.tables.HelpThreads; -import javax.annotation.Nonnull; -import java.util.*; +import java.util.HashSet; +import java.util.Set; /** * Remove all thread channels associated to a user when they leave the guild. @@ -28,12 +27,12 @@ public class OnGuildLeaveCloseThreadListener extends ListenerAdapter implements * * @param database database to use */ - public OnGuildLeaveCloseThreadListener(@NotNull Database database) { + public OnGuildLeaveCloseThreadListener(Database database) { this.database = database; } @Override - public void onGuildMemberRemove(@Nonnull GuildMemberRemoveEvent leaveEvent) { + public void onGuildMemberRemove(GuildMemberRemoveEvent leaveEvent) { Set channelIds = getThreadsCreatedByLeaver(leaveEvent.getUser().getIdLong()); for (long channelId : channelIds) { closeThread(channelId, leaveEvent); @@ -48,7 +47,7 @@ private Set getThreadsCreatedByLeaver(long leaverId) { .fetch(databaseMapper -> databaseMapper.getValue(HelpThreads.HELP_THREADS.CHANNEL_ID))); } - private void closeThread(long channelId, @NotNull GuildMemberRemoveEvent leaveEvent) { + private void closeThread(long channelId, GuildMemberRemoveEvent leaveEvent) { ThreadChannel threadChannel = leaveEvent.getGuild().getThreadChannelById(channelId); if (threadChannel == null) { logger.warn( diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/help/package-info.java b/application/src/main/java/org/togetherjava/tjbot/commands/help/package-info.java index d6deedbaf4..351316091a 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/help/package-info.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/help/package-info.java @@ -2,4 +2,7 @@ * This package offers all functionality for the help system. For example commands that let users * ask questions, such as {@link org.togetherjava.tjbot.commands.help.AskCommand}. */ +@ParametersAreNonnullByDefault package org.togetherjava.tjbot.commands.help; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/TeXCommand.java b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/TeXCommand.java index 00d1037c57..2b52137b76 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/TeXCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/TeXCommand.java @@ -6,7 +6,6 @@ import net.dv8tion.jda.api.interactions.commands.OptionType; import net.dv8tion.jda.api.interactions.components.buttons.Button; import net.dv8tion.jda.api.interactions.components.buttons.ButtonStyle; -import org.jetbrains.annotations.NotNull; import org.scilab.forge.jlatexmath.ParseException; import org.scilab.forge.jlatexmath.TeXConstants; import org.scilab.forge.jlatexmath.TeXFormula; @@ -15,6 +14,7 @@ import org.togetherjava.tjbot.commands.SlashCommandAdapter; import org.togetherjava.tjbot.commands.SlashCommandVisibility; +import javax.annotation.Nonnull; import javax.imageio.ImageIO; import java.awt.Color; import java.awt.Image; @@ -62,7 +62,7 @@ public TeXCommand() { } @Override - public void onSlashCommand(@NotNull final SlashCommandInteractionEvent event) { + public void onSlashCommand(final SlashCommandInteractionEvent event) { String latex = Objects.requireNonNull(event.getOption(LATEX_OPTION)).getAsString(); String userID = (Objects.requireNonNull(event.getMember()).getId()); TeXFormula formula; @@ -97,7 +97,8 @@ public void onSlashCommand(@NotNull final SlashCommandInteractionEvent event) { } } - private @NotNull Image renderImage(@NotNull TeXFormula formula) { + @Nonnull + private Image renderImage(TeXFormula formula) { Image image = formula.createBufferedImage(TeXConstants.STYLE_DISPLAY, DEFAULT_IMAGE_SIZE, FOREGROUND_COLOR, BACKGROUND_COLOR); @@ -107,8 +108,8 @@ public void onSlashCommand(@NotNull final SlashCommandInteractionEvent event) { return image; } - private void sendImage(@NotNull IDeferrableCallback event, @NotNull String userID, - @NotNull Image image) throws IOException { + private void sendImage(IDeferrableCallback event, String userID, Image image) + throws IOException { ByteArrayOutputStream renderedTextImageStream = getRenderedTextImageStream(image); event.getHook() .editOriginal(renderedTextImageStream.toByteArray(), "tex.png") @@ -116,9 +117,8 @@ private void sendImage(@NotNull IDeferrableCallback event, @NotNull String userI .queue(); } - @NotNull - private ByteArrayOutputStream getRenderedTextImageStream(@NotNull Image image) - throws IOException { + @Nonnull + private ByteArrayOutputStream getRenderedTextImageStream(Image image) throws IOException { BufferedImage renderedTextImage = new BufferedImage(image.getWidth(null), image.getHeight(null), BufferedImage.TYPE_4BYTE_ABGR); @@ -137,8 +137,8 @@ private ByteArrayOutputStream getRenderedTextImageStream(@NotNull Image image) * @param latex the latex to convert * @return the converted latex */ - @NotNull - private String convertInlineLatexToFull(@NotNull String latex) { + @Nonnull + private String convertInlineLatexToFull(String latex) { if (isInvalidInlineFormat(latex)) { throw new ParseException(INVALID_INLINE_FORMAT_ERROR_MESSAGE); } @@ -158,13 +158,12 @@ private String convertInlineLatexToFull(@NotNull String latex) { return sb.toString(); } - private boolean isInvalidInlineFormat(@NotNull String latex) { + private boolean isInvalidInlineFormat(String latex) { return latex.chars().filter(charAsInt -> charAsInt == '$').count() % 2 == 1; } @Override - public void onButtonClick(@NotNull final ButtonInteractionEvent event, - @NotNull final List args) { + public void onButtonClick(final ButtonInteractionEvent event, final List args) { if (!args.get(0).equals(Objects.requireNonNull(event.getMember()).getId())) { event.reply("You are not the person who executed the command, you cannot do that") .setEphemeral(true) diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/WolframAlphaCommand.java b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/WolframAlphaCommand.java index 90109410c2..8c650b60dc 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/WolframAlphaCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/WolframAlphaCommand.java @@ -6,7 +6,6 @@ import net.dv8tion.jda.api.interactions.callbacks.IDeferrableCallback; import net.dv8tion.jda.api.interactions.commands.OptionType; import net.dv8tion.jda.api.requests.restaction.WebhookMessageUpdateAction; -import org.jetbrains.annotations.NotNull; import org.togetherjava.tjbot.commands.SlashCommandAdapter; import org.togetherjava.tjbot.commands.SlashCommandVisibility; import org.togetherjava.tjbot.config.Config; @@ -39,7 +38,7 @@ public final class WolframAlphaCommand extends SlashCommandAdapter { * * @param config the config to use */ - public WolframAlphaCommand(@NotNull Config config) { + public WolframAlphaCommand(Config config) { super("wolfram-alpha", "Renders mathematical queries using WolframAlpha", SlashCommandVisibility.GUILD); getData().addOption(OptionType.STRING, QUERY_OPTION, "the query to send to WolframAlpha", @@ -48,7 +47,7 @@ public WolframAlphaCommand(@NotNull Config config) { } @Override - public void onSlashCommand(@NotNull SlashCommandInteractionEvent event) { + public void onSlashCommand(SlashCommandInteractionEvent event) { String query = event.getOption(QUERY_OPTION).getAsString(); WolframAlphaHandler handler = new WolframAlphaHandler(query); @@ -73,8 +72,8 @@ public void onSlashCommand(@NotNull SlashCommandInteractionEvent event) { .thenAccept(response -> sendResponse(response, event)); } - private static void sendResponse(@NotNull WolframAlphaHandler.HandlerResponse response, - @NotNull IDeferrableCallback event) { + private static void sendResponse(WolframAlphaHandler.HandlerResponse response, + IDeferrableCallback event) { WebhookMessageUpdateAction action = event.getHook().editOriginalEmbeds(response.embeds()); diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/WolframAlphaHandler.java b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/WolframAlphaHandler.java index 7362501427..753e2ddc70 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/WolframAlphaHandler.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/WolframAlphaHandler.java @@ -4,12 +4,12 @@ import io.mikael.urlbuilder.UrlBuilder; import net.dv8tion.jda.api.EmbedBuilder; import net.dv8tion.jda.api.entities.MessageEmbed; -import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.togetherjava.tjbot.commands.mathcommands.wolframalpha.api.Error; import org.togetherjava.tjbot.commands.mathcommands.wolframalpha.api.*; +import javax.annotation.Nonnull; import java.awt.Color; import java.awt.image.BufferedImage; import java.io.IOException; @@ -58,7 +58,7 @@ final class WolframAlphaHandler { * * @param query the original query send to the API */ - WolframAlphaHandler(@NotNull String query) { + WolframAlphaHandler(String query) { this.query = query; userApiQuery = UrlBuilder.fromString(USER_API_ENDPOINT) @@ -73,8 +73,8 @@ final class WolframAlphaHandler { * @param apiResponse response of the Wolfram Alpha API query * @return user-friendly message for display, as list of embeds */ - @NotNull - HandlerResponse handleApiResponse(@NotNull HttpResponse apiResponse) { + @Nonnull + HandlerResponse handleApiResponse(HttpResponse apiResponse) { // Check status code int statusCode = apiResponse.statusCode(); if (statusCode != HttpURLConnection.HTTP_OK) { @@ -111,7 +111,8 @@ HandlerResponse handleApiResponse(@NotNull HttpResponse apiResponse) { return handleSuccessfulResponse(queryResult); } - private @NotNull HandlerResponse handleMisunderstoodQuery(@NotNull QueryResult result) { + @Nonnull + private HandlerResponse handleMisunderstoodQuery(QueryResult result) { StringJoiner output = new StringJoiner("\n"); output.add("Sorry, I did not understand your query."); @@ -151,7 +152,8 @@ HandlerResponse handleApiResponse(@NotNull HttpResponse apiResponse) { return responseOf(output.toString()); } - private static @NotNull String createBulletPointList(Collection elements, + @Nonnull + private static String createBulletPointList(Collection elements, Function elementToText) { return elements.stream() .map(elementToText) @@ -159,7 +161,8 @@ HandlerResponse handleApiResponse(@NotNull HttpResponse apiResponse) { .collect(Collectors.joining("\n")); } - private @NotNull HandlerResponse handleSuccessfulResponse(@NotNull QueryResult queryResult) { + @Nonnull + private HandlerResponse handleSuccessfulResponse(QueryResult queryResult) { StringJoiner messages = new StringJoiner("\n\n"); messages.add("Click the link to see full results."); @@ -198,7 +201,8 @@ HandlerResponse handleApiResponse(@NotNull HttpResponse apiResponse) { return responseOf(messages.toString(), tilesToDisplay); } - private @NotNull HandlerResponse responseOf(@NotNull CharSequence text) { + @Nonnull + private HandlerResponse responseOf(CharSequence text) { MessageEmbed embed = new EmbedBuilder().setTitle(buildTitle(), userApiQuery) .setDescription(text) .setColor(AMBIENT_COLOR) @@ -207,8 +211,9 @@ HandlerResponse handleApiResponse(@NotNull HttpResponse apiResponse) { return new HandlerResponse(List.of(embed), List.of()); } - private @NotNull HandlerResponse responseOf(@NotNull CharSequence text, - @NotNull Collection tiles) { + @Nonnull + private HandlerResponse responseOf(CharSequence text, + Collection tiles) { List embeds = new ArrayList<>(); embeds.add(new EmbedBuilder().setTitle(buildTitle(), userApiQuery) .setDescription(text) @@ -232,16 +237,16 @@ HandlerResponse handleApiResponse(@NotNull HttpResponse apiResponse) { return new HandlerResponse(embeds, attachments); } - private @NotNull String buildTitle() { + @Nonnull + private String buildTitle() { return query + " - " + SERVICE_NAME; } - record HandlerResponse(@NotNull List embeds, - @NotNull List attachments) { + record HandlerResponse(List embeds, List attachments) { } - record Attachment(@NotNull String name, byte @NotNull [] data) { + record Attachment(String name, byte[] data) { @Override public boolean equals(Object o) { if (this == o) { diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/WolframAlphaImages.java b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/WolframAlphaImages.java index 47a3a9306e..3adbbf9ed7 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/WolframAlphaImages.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/WolframAlphaImages.java @@ -1,9 +1,9 @@ package org.togetherjava.tjbot.commands.mathcommands.wolframalpha; -import org.jetbrains.annotations.NotNull; import org.togetherjava.tjbot.commands.mathcommands.wolframalpha.api.SubPod; import org.togetherjava.tjbot.commands.mathcommands.wolframalpha.api.WolframAlphaImage; +import javax.annotation.Nonnull; import javax.imageio.ImageIO; import java.awt.Color; import java.awt.Font; @@ -39,7 +39,8 @@ private WolframAlphaImages() { throw new UnsupportedOperationException("Utility class, construction not supported"); } - static @NotNull BufferedImage renderTitle(@NotNull String title) { + @Nonnull + static BufferedImage renderTitle(String title) { Rectangle2D titleBounds = TITLE_FONT.getStringBounds(title, TITLE_RENDER_CONTEXT); int widthPx = (int) Math.ceil(titleBounds.getWidth()) + 2 * IMAGE_MARGIN_PX; int heightPx = (int) Math.ceil(titleBounds.getHeight()) + IMAGE_MARGIN_PX; @@ -55,7 +56,8 @@ private WolframAlphaImages() { return image; } - static @NotNull BufferedImage renderSubPod(@NotNull SubPod subPod) { + @Nonnull + static BufferedImage renderSubPod(SubPod subPod) { WolframAlphaImage sourceImage = subPod.getImage(); int widthPx = sourceImage.getWidth() + 2 * IMAGE_MARGIN_PX; @@ -75,12 +77,14 @@ private WolframAlphaImages() { return destinationImage; } - static @NotNull BufferedImage renderFooter() { + @Nonnull + static BufferedImage renderFooter() { return new BufferedImage(1, IMAGE_MARGIN_PX, BufferedImage.TYPE_4BYTE_ABGR); } - static @NotNull List combineImagesIntoTiles( - @NotNull Collection images, int maxTileHeight) { + @Nonnull + static List combineImagesIntoTiles(Collection images, + int maxTileHeight) { if (images.isEmpty()) { throw new IllegalArgumentException("Images must not be empty"); } @@ -120,8 +124,9 @@ private static boolean wouldTileBeTooLargeIfAddingImage(int tileHeight, int heig return tileHeight != 0 && tileHeight + heightOfImageToAdd > maxTileHeight; } - private static @NotNull BufferedImage combineImages( - @NotNull Collection images, int widthPx) { + @Nonnull + private static BufferedImage combineImages(Collection images, + int widthPx) { if (images.isEmpty()) { throw new IllegalArgumentException("Images must not be empty"); } @@ -146,7 +151,8 @@ private static boolean wouldTileBeTooLargeIfAddingImage(int tileHeight, int heig return destinationImage; } - static byte @NotNull [] imageToBytes(@NotNull RenderedImage img) { + @Nonnull + static byte[] imageToBytes(RenderedImage img) { try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { ImageIO.write(img, IMAGE_FORMAT, outputStream); return outputStream.toByteArray(); diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/package-info.java b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/package-info.java index f4513dbb6d..eb5e9633c4 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/package-info.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/package-info.java @@ -3,4 +3,7 @@ * WolframAlpha * API. */ +@ParametersAreNonnullByDefault package org.togetherjava.tjbot.commands.mathcommands.wolframalpha.api; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/package-info.java b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/package-info.java index d864dfb435..eb9db3d28a 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/package-info.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/package-info.java @@ -2,4 +2,7 @@ * This packages offers all the functionality for the wolfram-alpha command. Sending queries to * their official API, rendering results and displaying them. */ +@ParametersAreNonnullByDefault package org.togetherjava.tjbot.commands.mathcommands.wolframalpha; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/ActionRecord.java b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/ActionRecord.java index 910b7c8663..f7ca36833a 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/ActionRecord.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/ActionRecord.java @@ -1,9 +1,9 @@ package org.togetherjava.tjbot.commands.moderation; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import org.togetherjava.tjbot.db.generated.tables.records.ModerationActionsRecord; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; import java.time.Instant; /** @@ -20,9 +20,8 @@ * {@code null} * @param reason the reason why this action was executed */ -public record ActionRecord(int caseId, @NotNull Instant issuedAt, long guildId, long authorId, - long targetId, @NotNull ModerationAction actionType, @Nullable Instant actionExpiresAt, - @NotNull String reason) { +public record ActionRecord(int caseId, Instant issuedAt, long guildId, long authorId, long targetId, + ModerationAction actionType, @Nullable Instant actionExpiresAt, String reason) { /** * Creates the action record that corresponds to the given action entry from the database table. @@ -30,8 +29,8 @@ public record ActionRecord(int caseId, @NotNull Instant issuedAt, long guildId, * @param action the action to convert * @return the corresponding action record */ - @SuppressWarnings("StaticMethodOnlyUsedInOneClass") - static @NotNull ActionRecord of(@NotNull ModerationActionsRecord action) { + @Nonnull + static ActionRecord of(ModerationActionsRecord action) { return new ActionRecord(action.getCaseId(), action.getIssuedAt(), action.getGuildId(), action.getAuthorId(), action.getTargetId(), ModerationAction.valueOf(action.getActionType()), action.getActionExpiresAt(), diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/AuditCommand.java b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/AuditCommand.java index 2774271f5f..c59884b593 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/AuditCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/AuditCommand.java @@ -14,11 +14,11 @@ import net.dv8tion.jda.api.requests.RestAction; import net.dv8tion.jda.api.utils.TimeUtil; import net.dv8tion.jda.internal.requests.CompletedRestAction; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import org.togetherjava.tjbot.commands.SlashCommandAdapter; import org.togetherjava.tjbot.commands.SlashCommandVisibility; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; import java.time.Instant; import java.time.ZoneOffset; import java.util.*; @@ -45,7 +45,7 @@ public final class AuditCommand extends SlashCommandAdapter { * * @param actionsStore used to store actions issued by this command */ - public AuditCommand(@NotNull ModerationActionsStore actionsStore) { + public AuditCommand(ModerationActionsStore actionsStore) { super(COMMAND_NAME, "Lists all moderation actions that have been taken against a user", SlashCommandVisibility.GUILD); @@ -56,7 +56,7 @@ public AuditCommand(@NotNull ModerationActionsStore actionsStore) { } @Override - public void onSlashCommand(@NotNull SlashCommandInteractionEvent event) { + public void onSlashCommand(SlashCommandInteractionEvent event) { OptionMapping targetOption = Objects.requireNonNull(event.getOption(TARGET_OPTION), "The target is null"); User target = targetOption.getAsUser(); @@ -73,9 +73,8 @@ public void onSlashCommand(@NotNull SlashCommandInteractionEvent event) { event.getJDA()).flatMap(event::reply).queue(); } - @SuppressWarnings("BooleanMethodNameMustStartWithQuestion") - private boolean handleChecks(@NotNull Member bot, @NotNull Member author, - @Nullable Member target, @NotNull IReplyCallback event) { + private boolean handleChecks(Member bot, Member author, @Nullable Member target, + IReplyCallback event) { // Member doesn't exist if attempting to audit a user who is not part of the guild. if (target == null) { return true; @@ -88,8 +87,9 @@ private boolean handleChecks(@NotNull Member bot, @NotNull Member author, * can contain {@link AuditCommand#MAX_PAGE_LENGTH} actions, {@code -1} encodes the last * page */ - private @NotNull RestAction auditUser(long guildId, long targetId, long callerId, - int pageNumber, @NotNull JDA jda) { + @Nonnull + private RestAction auditUser(long guildId, long targetId, long callerId, + int pageNumber, JDA jda) { List actions = actionsStore.getActionsByTargetAscending(guildId, targetId); List> groupedActions = groupActionsByPages(actions); int totalPages = groupedActions.size(); @@ -109,8 +109,8 @@ private boolean handleChecks(@NotNull Member bot, @NotNull Member author, guildId, targetId, callerId)); } - private @NotNull List> groupActionsByPages( - @NotNull List actions) { + @Nonnull + private List> groupActionsByPages(List actions) { List> groupedActions = new ArrayList<>(); for (int i = 0; i < actions.size(); i++) { if (i % AuditCommand.MAX_PAGE_LENGTH == 0) { @@ -127,16 +127,16 @@ private static int clamp(int minInclusive, int value, int maxInclusive) { return Math.min(Math.max(minInclusive, value), maxInclusive); } - private static @NotNull EmbedBuilder createSummaryEmbed(@NotNull User user, - @NotNull Collection actions) { + @Nonnull + private static EmbedBuilder createSummaryEmbed(User user, Collection actions) { return new EmbedBuilder().setTitle("Audit log of **%s**".formatted(user.getAsTag())) .setAuthor(user.getName(), null, user.getAvatarUrl()) .setDescription(createSummaryMessageDescription(actions)) .setColor(ModerationUtils.AMBIENT_COLOR); } - private static @NotNull String createSummaryMessageDescription( - @NotNull Collection actions) { + @Nonnull + private static String createSummaryMessageDescription(Collection actions) { int actionAmount = actions.size(); String shortSummary = "There are **%s actions** against the user." @@ -161,9 +161,10 @@ private static int clamp(int minInclusive, int value, int maxInclusive) { return shortSummary + "\n" + typeCountSummary; } - private @NotNull RestAction attachEmbedFields(@NotNull EmbedBuilder auditEmbed, - @NotNull List> groupedActions, int pageNumber, - int totalPages, @NotNull JDA jda) { + @Nonnull + private RestAction attachEmbedFields(EmbedBuilder auditEmbed, + List> groupedActions, int pageNumber, int totalPages, + JDA jda) { if (groupedActions.isEmpty()) { return new CompletedRestAction<>(jda, auditEmbed); } @@ -180,8 +181,8 @@ private static int clamp(int minInclusive, int value, int maxInclusive) { }); } - private static @NotNull RestAction actionToField( - @NotNull ActionRecord action, @NotNull JDA jda) { + @Nonnull + private static RestAction actionToField(ActionRecord action, JDA jda) { return jda.retrieveUserById(action.authorId()) .map(author -> author == null ? "(unknown user)" : author.getAsTag()) .map(authorText -> { @@ -199,12 +200,14 @@ private static int clamp(int minInclusive, int value, int maxInclusive) { }); } - private static @NotNull String formatTime(@NotNull Instant when) { + @Nonnull + private static String formatTime(Instant when) { return TimeUtil.getDateTimeString(when.atOffset(ZoneOffset.UTC)); } - private @NotNull Message attachPageTurnButtons(@NotNull EmbedBuilder auditEmbed, int pageNumber, - int totalPages, long guildId, long targetId, long callerId) { + @Nonnull + private Message attachPageTurnButtons(EmbedBuilder auditEmbed, int pageNumber, int totalPages, + long guildId, long targetId, long callerId) { var messageBuilder = new MessageBuilder(auditEmbed.build()); if (totalPages <= 1) { @@ -216,7 +219,8 @@ private static int clamp(int minInclusive, int value, int maxInclusive) { return messageBuilder.setActionRows(pageTurnButtons).build(); } - private @NotNull ActionRow createPageTurnButtons(long guildId, long targetId, long callerId, + @Nonnull + private ActionRow createPageTurnButtons(long guildId, long targetId, long callerId, int pageNumber, int totalPages) { int previousButtonTurnPageBy = -1; Button previousButton = createPageTurnButton(PREVIOUS_BUTTON_LABEL, guildId, targetId, @@ -235,15 +239,16 @@ private static int clamp(int minInclusive, int value, int maxInclusive) { return ActionRow.of(previousButton, nextButton); } - private @NotNull Button createPageTurnButton(@NotNull String label, long guildId, long targetId, - long callerId, long pageNumber, int turnPageBy) { + @Nonnull + private Button createPageTurnButton(String label, long guildId, long targetId, long callerId, + long pageNumber, int turnPageBy) { return Button.primary(generateComponentId(String.valueOf(guildId), String.valueOf(targetId), String.valueOf(callerId), String.valueOf(pageNumber), String.valueOf(turnPageBy)), label); } @Override - public void onButtonClick(@NotNull ButtonInteractionEvent event, @NotNull List args) { + public void onButtonClick(ButtonInteractionEvent event, List args) { long commandUserId = Long.parseLong(args.get(2)); long buttonUserId = event.getMember().getIdLong(); diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/BanCommand.java b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/BanCommand.java index b59450c4ad..f633921fc9 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/BanCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/BanCommand.java @@ -14,13 +14,13 @@ import net.dv8tion.jda.api.requests.RestAction; import net.dv8tion.jda.api.requests.restaction.AuditableRestAction; import net.dv8tion.jda.api.utils.Result; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.togetherjava.tjbot.commands.SlashCommandAdapter; import org.togetherjava.tjbot.commands.SlashCommandVisibility; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; import java.time.Instant; import java.util.List; import java.util.Objects; @@ -52,7 +52,7 @@ public final class BanCommand extends SlashCommandAdapter { * * @param actionsStore used to store actions issued by this command */ - public BanCommand(@NotNull ModerationActionsStore actionsStore) { + public BanCommand(ModerationActionsStore actionsStore) { super(COMMAND_NAME, "Bans the given user from the server", SlashCommandVisibility.GUILD); OptionData durationData = new OptionData(OptionType.STRING, DURATION_OPTION, @@ -69,8 +69,8 @@ public BanCommand(@NotNull ModerationActionsStore actionsStore) { this.actionsStore = Objects.requireNonNull(actionsStore); } - private static RestAction handleAlreadyBanned(@NotNull Guild.Ban ban, - @NotNull IReplyCallback event) { + private static RestAction handleAlreadyBanned(Guild.Ban ban, + IReplyCallback event) { String reason = ban.getReason(); String reasonText = reason == null || reason.isBlank() ? "" : " (reason: %s)".formatted(reason); @@ -80,9 +80,9 @@ private static RestAction handleAlreadyBanned(@NotNull Guild.Ba return event.reply(message).setEphemeral(true); } - private static RestAction sendDm(@NotNull ISnowflake target, - @Nullable ModerationUtils.TemporaryData temporaryData, @NotNull String reason, - @NotNull Guild guild, @NotNull GenericEvent event) { + private static RestAction sendDm(ISnowflake target, + @Nullable ModerationUtils.TemporaryData temporaryData, String reason, Guild guild, + GenericEvent event) { String durationMessage = temporaryData == null ? "permanently" : "for " + temporaryData.duration(); String dmMessage = @@ -100,9 +100,9 @@ private static RestAction sendDm(@NotNull ISnowflake target, .map(Result::isSuccess); } - private static @NotNull MessageEmbed sendFeedback(boolean hasSentDm, @NotNull User target, - @NotNull Member author, @Nullable ModerationUtils.TemporaryData temporaryData, - @NotNull String reason) { + @Nonnull + private static MessageEmbed sendFeedback(boolean hasSentDm, User target, Member author, + @Nullable ModerationUtils.TemporaryData temporaryData, String reason) { String durationText = "The ban duration is: " + (temporaryData == null ? "permanent" : temporaryData.duration()); String dmNoticeText = ""; @@ -113,9 +113,9 @@ private static RestAction sendDm(@NotNull ISnowflake target, durationText + dmNoticeText, reason); } + @Nonnull private static Optional> handleNotAlreadyBannedResponse( - @NotNull Throwable alreadyBannedFailure, @NotNull IReplyCallback event, - @NotNull Guild guild, @NotNull User target) { + Throwable alreadyBannedFailure, IReplyCallback event, Guild guild, User target) { if (alreadyBannedFailure instanceof ErrorResponseException errorResponseException) { if (errorResponseException.getErrorResponse() == ErrorResponse.UNKNOWN_BAN) { return Optional.empty(); @@ -136,11 +136,10 @@ private static Optional> handleNotAlreadyBannedRespo .setEphemeral(true)); } - @SuppressWarnings("MethodWithTooManyParameters") - private RestAction banUserFlow(@NotNull User target, @NotNull Member author, - @Nullable ModerationUtils.TemporaryData temporaryData, @NotNull String reason, - int deleteHistoryDays, @NotNull Guild guild, - @NotNull SlashCommandInteractionEvent event) { + @Nonnull + private RestAction banUserFlow(User target, Member author, + @Nullable ModerationUtils.TemporaryData temporaryData, String reason, + int deleteHistoryDays, Guild guild, SlashCommandInteractionEvent event) { return sendDm(target, temporaryData, reason, guild, event) .flatMap(hasSentDm -> banUser(target, author, temporaryData, reason, deleteHistoryDays, guild).map(banResult -> hasSentDm)) @@ -148,10 +147,10 @@ private RestAction banUserFlow(@NotNull User target, @NotNull M .flatMap(event::replyEmbeds); } - @SuppressWarnings("MethodWithTooManyParameters") - private AuditableRestAction banUser(@NotNull User target, @NotNull Member author, - @Nullable ModerationUtils.TemporaryData temporaryData, @NotNull String reason, - int deleteHistoryDays, @NotNull Guild guild) { + @Nonnull + private AuditableRestAction banUser(User target, Member author, + @Nullable ModerationUtils.TemporaryData temporaryData, String reason, + int deleteHistoryDays, Guild guild) { String durationMessage = temporaryData == null ? "permanently" : "for " + temporaryData.duration(); logger.info( @@ -167,9 +166,8 @@ private AuditableRestAction banUser(@NotNull User target, @NotNull Member } @SuppressWarnings({"BooleanMethodNameMustStartWithQuestion", "MethodWithTooManyParameters"}) - private boolean handleChecks(@NotNull Member bot, @NotNull Member author, - @Nullable Member target, @NotNull CharSequence reason, @NotNull Guild guild, - @NotNull IReplyCallback event) { + private boolean handleChecks(Member bot, Member author, @Nullable Member target, + CharSequence reason, Guild guild, IReplyCallback event) { // Member doesn't exist if attempting to ban a user who is not part of the guild. if (target != null && !ModerationUtils.handleCanInteractWithTarget(ACTION_VERB, bot, author, target, event)) { @@ -180,14 +178,14 @@ private boolean handleChecks(@NotNull Member bot, @NotNull Member author, return false; } if (!ModerationUtils.handleHasAuthorPermissions(ACTION_VERB, Permission.BAN_MEMBERS, author, - guild, event)) { + event)) { return false; } return ModerationUtils.handleReason(reason, event); } @Override - public void onSlashCommand(@NotNull SlashCommandInteractionEvent event) { + public void onSlashCommand(SlashCommandInteractionEvent event) { OptionMapping targetOption = Objects.requireNonNull(event.getOption(TARGET_OPTION), "The target is null"); User target = targetOption.getAsUser(); diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/KickCommand.java b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/KickCommand.java index 8a079587f9..1b6a7e23ca 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/KickCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/KickCommand.java @@ -12,13 +12,13 @@ import net.dv8tion.jda.api.requests.RestAction; import net.dv8tion.jda.api.requests.restaction.AuditableRestAction; import net.dv8tion.jda.api.utils.Result; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.togetherjava.tjbot.commands.SlashCommandAdapter; import org.togetherjava.tjbot.commands.SlashCommandVisibility; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; import java.util.Objects; /** @@ -41,7 +41,7 @@ public final class KickCommand extends SlashCommandAdapter { * * @param actionsStore used to store actions issued by this command */ - public KickCommand(@NotNull ModerationActionsStore actionsStore) { + public KickCommand(ModerationActionsStore actionsStore) { super(COMMAND_NAME, "Kicks the given user from the server", SlashCommandVisibility.GUILD); getData().addOption(OptionType.USER, TARGET_OPTION, "The user who you want to kick", true) @@ -50,15 +50,14 @@ public KickCommand(@NotNull ModerationActionsStore actionsStore) { this.actionsStore = Objects.requireNonNull(actionsStore); } - private static void handleAbsentTarget(@NotNull IReplyCallback event) { + private static void handleAbsentTarget(IReplyCallback event) { event.reply("I can not kick the given user since they are not part of the guild anymore.") .setEphemeral(true) .queue(); } - private void kickUserFlow(@NotNull Member target, @NotNull Member author, - @NotNull String reason, @NotNull Guild guild, - @NotNull SlashCommandInteractionEvent event) { + private void kickUserFlow(Member target, Member author, String reason, Guild guild, + SlashCommandInteractionEvent event) { sendDm(target, reason, guild, event) .flatMap(hasSentDm -> kickUser(target, author, reason, guild) .map(kickResult -> hasSentDm)) @@ -67,8 +66,9 @@ private void kickUserFlow(@NotNull Member target, @NotNull Member author, .queue(); } - private static RestAction sendDm(@NotNull ISnowflake target, @NotNull String reason, - @NotNull Guild guild, @NotNull GenericEvent event) { + @Nonnull + private static RestAction sendDm(ISnowflake target, String reason, Guild guild, + GenericEvent event) { return event.getJDA() .openPrivateChannelById(target.getId()) .flatMap(channel -> channel.sendMessage( @@ -82,8 +82,9 @@ private static RestAction sendDm(@NotNull ISnowflake target, @NotNull S .map(Result::isSuccess); } - private AuditableRestAction kickUser(@NotNull Member target, @NotNull Member author, - @NotNull String reason, @NotNull Guild guild) { + @Nonnull + private AuditableRestAction kickUser(Member target, Member author, String reason, + Guild guild) { logger.info("'{}' ({}) kicked the user '{}' ({}) from guild '{}' for reason '{}'.", author.getUser().getAsTag(), author.getId(), target.getUser().getAsTag(), target.getId(), guild.getName(), reason); @@ -94,8 +95,9 @@ private AuditableRestAction kickUser(@NotNull Member target, @NotNull Memb return guild.kick(target, reason).reason(reason); } - private static @NotNull MessageEmbed sendFeedback(boolean hasSentDm, @NotNull Member target, - @NotNull Member author, @NotNull String reason) { + @Nonnull + private static MessageEmbed sendFeedback(boolean hasSentDm, Member target, Member author, + String reason) { String dmNoticeText = ""; if (!hasSentDm) { dmNoticeText = "(Unable to send them a DM.)"; @@ -104,10 +106,9 @@ private AuditableRestAction kickUser(@NotNull Member target, @NotNull Memb target.getUser(), dmNoticeText, reason); } - @SuppressWarnings({"BooleanMethodNameMustStartWithQuestion", "MethodWithTooManyParameters"}) - private boolean handleChecks(@NotNull Member bot, @NotNull Member author, - @Nullable Member target, @NotNull CharSequence reason, @NotNull Guild guild, - @NotNull IReplyCallback event) { + @Nonnull + private boolean handleChecks(Member bot, Member author, @Nullable Member target, + CharSequence reason, Guild guild, IReplyCallback event) { // Member doesn't exist if attempting to kick a user who is not part of the guild anymore. if (target == null) { handleAbsentTarget(event); @@ -121,14 +122,14 @@ private boolean handleChecks(@NotNull Member bot, @NotNull Member author, return false; } if (!ModerationUtils.handleHasAuthorPermissions(ACTION_VERB, Permission.KICK_MEMBERS, - author, guild, event)) { + author, event)) { return false; } return ModerationUtils.handleReason(reason, event); } @Override - public void onSlashCommand(@NotNull SlashCommandInteractionEvent event) { + public void onSlashCommand(SlashCommandInteractionEvent event) { Member target = Objects.requireNonNull(event.getOption(TARGET_OPTION), "The target is null") .getAsMember(); Member author = Objects.requireNonNull(event.getMember(), "The author is null"); diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/ModerationAction.java b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/ModerationAction.java index 76121063ef..047a371300 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/ModerationAction.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/ModerationAction.java @@ -1,6 +1,6 @@ package org.togetherjava.tjbot.commands.moderation; -import org.jetbrains.annotations.NotNull; +import javax.annotation.Nonnull; /** * All available moderation actions. @@ -51,7 +51,7 @@ public enum ModerationAction { * @param verb the verb of the action, as it would be used in a sentence, such as "banned" or * "kicked" */ - ModerationAction(@NotNull String verb) { + ModerationAction(String verb) { this.verb = verb; } @@ -62,7 +62,8 @@ public enum ModerationAction { * * @return the verb of this action */ - public @NotNull String getVerb() { + @Nonnull + public String getVerb() { return verb; } } diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/ModerationActionsStore.java b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/ModerationActionsStore.java index 3ee603b93a..89ffd7c231 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/ModerationActionsStore.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/ModerationActionsStore.java @@ -1,12 +1,12 @@ package org.togetherjava.tjbot.commands.moderation; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import org.jooq.Condition; import org.togetherjava.tjbot.db.Database; import org.togetherjava.tjbot.db.generated.tables.ModerationActions; import org.togetherjava.tjbot.db.generated.tables.records.ModerationActionsRecord; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; import java.time.Instant; import java.util.List; import java.util.Objects; @@ -35,7 +35,7 @@ public final class ModerationActionsStore { * * @param database the database to write and retrieve actions from */ - public ModerationActionsStore(@NotNull Database database) { + public ModerationActionsStore(Database database) { this.database = Objects.requireNonNull(database); } @@ -45,7 +45,8 @@ public ModerationActionsStore(@NotNull Database database) { * * @return a list of all expired actions, chronologically ascending */ - public @NotNull List getExpiredActionsAscending() { + @Nonnull + public List getExpiredActionsAscending() { return getActionsAscendingWhere( ModerationActions.MODERATION_ACTIONS.ACTION_EXPIRES_AT.isNotNull() .and(ModerationActions.MODERATION_ACTIONS.ACTION_EXPIRES_AT @@ -61,8 +62,8 @@ public ModerationActionsStore(@NotNull Database database) { * @param actionType the type of action to filter for * @return a list of all actions with the given type, chronologically ascending */ - public @NotNull List getActionsByTypeAscending(long guildId, - @NotNull ModerationAction actionType) { + @Nonnull + public List getActionsByTypeAscending(long guildId, ModerationAction actionType) { Objects.requireNonNull(actionType); return getActionsFromGuildAscending(guildId, @@ -78,7 +79,8 @@ public ModerationActionsStore(@NotNull Database database) { * @param targetId the id of the target user to filter for * @return a list of all actions executed against the target, chronologically ascending */ - public @NotNull List getActionsByTargetAscending(long guildId, long targetId) { + @Nonnull + public List getActionsByTargetAscending(long guildId, long targetId) { return getActionsFromGuildAscending(guildId, ModerationActions.MODERATION_ACTIONS.TARGET_ID.eq(targetId)); } @@ -92,7 +94,8 @@ public ModerationActionsStore(@NotNull Database database) { * @param authorId the id of the author user to filter for * @return a list of all actions executed by the author, chronologically ascending */ - public @NotNull List getActionsByAuthorAscending(long guildId, long authorId) { + @Nonnull + public List getActionsByAuthorAscending(long guildId, long authorId) { return getActionsFromGuildAscending(guildId, ModerationActions.MODERATION_ACTIONS.AUTHOR_ID.eq(authorId)); } @@ -107,8 +110,9 @@ public ModerationActionsStore(@NotNull Database database) { * @param actionType the type of the action * @return the last action issued against the given user of the given type, if present */ - public @NotNull Optional findLastActionAgainstTargetByType(long guildId, - long targetId, @NotNull ModerationAction actionType) { + @Nonnull + public Optional findLastActionAgainstTargetByType(long guildId, long targetId, + ModerationAction actionType) { return database .read(context -> context.selectFrom(ModerationActions.MODERATION_ACTIONS) .where(ModerationActions.MODERATION_ACTIONS.GUILD_ID.eq(guildId) @@ -126,7 +130,8 @@ public ModerationActionsStore(@NotNull Database database) { * @param caseId the actions' case id to search for * @return the action with the given case id, if present */ - public @NotNull Optional findActionByCaseId(int caseId) { + @Nonnull + public Optional findActionByCaseId(int caseId) { return database .read(context -> context.selectFrom(ModerationActions.MODERATION_ACTIONS) .where(ModerationActions.MODERATION_ACTIONS.CASE_ID.eq(caseId)) @@ -152,10 +157,8 @@ public ModerationActionsStore(@NotNull Database database) { * @param reason the reason why this action was executed * @return the unique case id associated with the action */ - @SuppressWarnings("MethodWithTooManyParameters") - public int addAction(long guildId, long authorId, long targetId, - @NotNull ModerationAction actionType, @Nullable Instant actionExpiresAt, - @NotNull String reason) { + public int addAction(long guildId, long authorId, long targetId, ModerationAction actionType, + @Nullable Instant actionExpiresAt, String reason) { Objects.requireNonNull(actionType); Objects.requireNonNull(reason); @@ -174,15 +177,16 @@ public int addAction(long guildId, long authorId, long targetId, }); } - private @NotNull List getActionsFromGuildAscending(long guildId, - @NotNull Condition condition) { + @Nonnull + private List getActionsFromGuildAscending(long guildId, Condition condition) { Objects.requireNonNull(condition); return getActionsAscendingWhere( ModerationActions.MODERATION_ACTIONS.GUILD_ID.eq(guildId).and(condition)); } - private @NotNull List getActionsAscendingWhere(@NotNull Condition condition) { + @Nonnull + private List getActionsAscendingWhere(Condition condition) { Objects.requireNonNull(condition); return database.read(context -> context.selectFrom(ModerationActions.MODERATION_ACTIONS) diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/ModerationUtils.java b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/ModerationUtils.java index 0450e92c49..bf36aed4ed 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/ModerationUtils.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/ModerationUtils.java @@ -4,13 +4,13 @@ import net.dv8tion.jda.api.Permission; import net.dv8tion.jda.api.entities.*; import net.dv8tion.jda.api.interactions.callbacks.IReplyCallback; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.togetherjava.tjbot.config.Config; -import java.awt.*; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.awt.Color; import java.time.Instant; import java.time.temporal.ChronoUnit; import java.time.temporal.TemporalUnit; @@ -51,8 +51,7 @@ private ModerationUtils() { * @param event the event used to respond to the user * @return whether the reason is valid */ - @SuppressWarnings("BooleanMethodNameMustStartWithQuestion") - static boolean handleReason(@NotNull CharSequence reason, @NotNull IReplyCallback event) { + static boolean handleReason(CharSequence reason, IReplyCallback event) { if (reason.length() <= REASON_MAX_LENGTH) { return true; } @@ -78,9 +77,8 @@ static boolean handleReason(@NotNull CharSequence reason, @NotNull IReplyCallbac * @param event the event used to respond to the user * @return Whether the author and bot can interact with the target user */ - @SuppressWarnings("BooleanMethodNameMustStartWithQuestion") - static boolean handleCanInteractWithTarget(@NotNull String actionVerb, @NotNull Member bot, - @NotNull Member author, @NotNull Member target, @NotNull IReplyCallback event) { + static boolean handleCanInteractWithTarget(String actionVerb, Member bot, Member author, + Member target, IReplyCallback event) { String targetTag = target.getUser().getAsTag(); if (!author.canInteract(target)) { event @@ -113,9 +111,8 @@ static boolean handleCanInteractWithTarget(@NotNull String actionVerb, @NotNull * @param event the event used to respond to the user * @return Whether the author and bot can interact with the role */ - @SuppressWarnings("BooleanMethodNameMustStartWithQuestion") - static boolean handleCanInteractWithRole(@NotNull Member bot, @NotNull Member author, - @NotNull Role role, @NotNull IReplyCallback event) { + static boolean handleCanInteractWithRole(Member bot, Member author, Role role, + IReplyCallback event) { if (!author.canInteract(role)) { event .reply("The role %s is too powerful for you to interact with." @@ -148,10 +145,8 @@ static boolean handleCanInteractWithRole(@NotNull Member bot, @NotNull Member au * @param event the event used to respond to the user * @return Whether the bot has the required permission */ - @SuppressWarnings("BooleanMethodNameMustStartWithQuestion") - static boolean handleHasBotPermissions(@NotNull String actionVerb, - @NotNull Permission permission, @NotNull IPermissionHolder bot, @NotNull Guild guild, - @NotNull IReplyCallback event) { + static boolean handleHasBotPermissions(String actionVerb, Permission permission, + IPermissionHolder bot, Guild guild, IReplyCallback event) { if (!bot.hasPermission(permission)) { event .reply("I can not %s users in this guild since I do not have the %s permission." @@ -166,8 +161,7 @@ static boolean handleHasBotPermissions(@NotNull String actionVerb, return true; } - private static void handleAbsentTarget(@NotNull String actionVerb, - @NotNull IReplyCallback event) { + private static void handleAbsentTarget(String actionVerb, IReplyCallback event) { event .reply("I can not %s the given user since they are not part of the guild anymore." .formatted(actionVerb)) @@ -202,11 +196,12 @@ private static void handleAbsentTarget(@NotNull String actionVerb, * @param event the event used to respond to the user * @return Whether the bot and the author have enough permission */ - @SuppressWarnings({"MethodWithTooManyParameters", "BooleanMethodNameMustStartWithQuestion", - "squid:S107"}) - static boolean handleRoleChangeChecks(@Nullable Role role, @NotNull String actionVerb, - @Nullable Member target, @NotNull Member bot, @NotNull Member author, - @NotNull Guild guild, @NotNull CharSequence reason, @NotNull IReplyCallback event) { + // Sonar complains about having too many parameters. Not incorrect, but not easy to work around + // for now + @SuppressWarnings({"MethodWithTooManyParameters", "squid:S107"}) + static boolean handleRoleChangeChecks(@Nullable Role role, String actionVerb, + @Nullable Member target, Member bot, Member author, Guild guild, CharSequence reason, + IReplyCallback event) { if (role == null) { event .reply("Can not %s the user, unable to find the corresponding role on this server" @@ -248,10 +243,8 @@ static boolean handleRoleChangeChecks(@Nullable Role role, @NotNull String actio * @param event the event used to respond to the user * @return Whether the author has the required permission */ - @SuppressWarnings("BooleanMethodNameMustStartWithQuestion") - static boolean handleHasAuthorPermissions(@NotNull String actionVerb, - @NotNull Permission permission, @NotNull IPermissionHolder author, @NotNull Guild guild, - @NotNull IReplyCallback event) { + static boolean handleHasAuthorPermissions(String actionVerb, Permission permission, + IPermissionHolder author, IReplyCallback event) { if (!author.hasPermission(permission)) { event .reply("You can not %s users in this guild since you do not have the %s permission." @@ -277,9 +270,9 @@ static boolean handleHasAuthorPermissions(@NotNull String actionVerb, * @param reason an optional reason for why the action is executed, {@code null} if not desired * @return the created response */ - static @NotNull MessageEmbed createActionResponse(@NotNull User author, - @NotNull ModerationAction action, @NotNull User target, @Nullable String extraMessage, - @Nullable String reason) { + @Nonnull + static MessageEmbed createActionResponse(User author, ModerationAction action, User target, + @Nullable String extraMessage, @Nullable String reason) { String description = "%s **%s** (id: %s).".formatted(action.getVerb(), target.getAsTag(), target.getId()); if (extraMessage != null && !extraMessage.isBlank()) { @@ -301,7 +294,7 @@ static boolean handleHasAuthorPermissions(@NotNull String actionVerb, * @param config the config used to identify the muted role * @return predicate that matches the name of the muted role */ - public static Predicate getIsMutedRolePredicate(@NotNull Config config) { + public static Predicate getIsMutedRolePredicate(Config config) { return Pattern.compile(config.getMutedRolePattern()).asMatchPredicate(); } @@ -312,8 +305,8 @@ public static Predicate getIsMutedRolePredicate(@NotNull Config config) * @param config the config used to identify the muted role * @return the muted role, if found */ - public static @NotNull Optional getMutedRole(@NotNull Guild guild, - @NotNull Config config) { + @Nonnull + public static Optional getMutedRole(Guild guild, Config config) { Predicate isMutedRole = getIsMutedRolePredicate(config); return guild.getRoles().stream().filter(role -> isMutedRole.test(role.getName())).findAny(); } @@ -324,7 +317,7 @@ public static Predicate getIsMutedRolePredicate(@NotNull Config config) * @param config the config used to identify the quarantined role * @return predicate that matches the name of the quarantined role */ - public static Predicate getIsQuarantinedRolePredicate(@NotNull Config config) { + public static Predicate getIsQuarantinedRolePredicate(Config config) { return Pattern.compile(config.getQuarantinedRolePattern()).asMatchPredicate(); } @@ -335,8 +328,8 @@ public static Predicate getIsQuarantinedRolePredicate(@NotNull Config co * @param config the config used to identify the quarantined role * @return the quarantined role, if found */ - public static @NotNull Optional getQuarantinedRole(@NotNull Guild guild, - @NotNull Config config) { + @Nonnull + public static Optional getQuarantinedRole(Guild guild, Config config) { Predicate isQuarantinedRole = getIsQuarantinedRolePredicate(config); return guild.getRoles() .stream() @@ -353,7 +346,8 @@ public static Predicate getIsQuarantinedRolePredicate(@NotNull Config co * @return the temporary data represented by the given duration or empty if the duration is * {@code "permanent"} */ - static @NotNull Optional computeTemporaryData(@NotNull String durationText) { + @Nonnull + static Optional computeTemporaryData(String durationText) { if (PERMANENT_DURATION.equals(durationText)) { return Optional.empty(); } @@ -378,6 +372,6 @@ public static Predicate getIsQuarantinedRolePredicate(@NotNull Config co * @param duration a human-readable text representing the duration of the temporary action, such * as {@code "1 day"}. */ - record TemporaryData(@NotNull Instant expiresAt, @NotNull String duration) { + record TemporaryData(Instant expiresAt, String duration) { } } diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/MuteCommand.java b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/MuteCommand.java index 4c9e9622b7..7e3f40977d 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/MuteCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/MuteCommand.java @@ -9,14 +9,14 @@ import net.dv8tion.jda.api.requests.RestAction; import net.dv8tion.jda.api.requests.restaction.AuditableRestAction; import net.dv8tion.jda.api.utils.Result; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.togetherjava.tjbot.commands.SlashCommandAdapter; import org.togetherjava.tjbot.commands.SlashCommandVisibility; import org.togetherjava.tjbot.config.Config; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; import java.time.Instant; import java.util.List; import java.util.Objects; @@ -48,7 +48,7 @@ public final class MuteCommand extends SlashCommandAdapter { * @param actionsStore used to store actions issued by this command * @param config the config to use for this */ - public MuteCommand(@NotNull ModerationActionsStore actionsStore, @NotNull Config config) { + public MuteCommand(ModerationActionsStore actionsStore, Config config) { super(COMMAND_NAME, "Mutes the given user so that they can not send messages anymore", SlashCommandVisibility.GUILD); @@ -64,13 +64,14 @@ public MuteCommand(@NotNull ModerationActionsStore actionsStore, @NotNull Config this.actionsStore = Objects.requireNonNull(actionsStore); } - private static void handleAlreadyMutedTarget(@NotNull IReplyCallback event) { + private static void handleAlreadyMutedTarget(IReplyCallback event) { event.reply("The user is already muted.").setEphemeral(true).queue(); } - private static RestAction sendDm(@NotNull ISnowflake target, - @Nullable ModerationUtils.TemporaryData temporaryData, @NotNull String reason, - @NotNull Guild guild, @NotNull GenericEvent event) { + @Nonnull + private static RestAction sendDm(ISnowflake target, + @Nullable ModerationUtils.TemporaryData temporaryData, String reason, Guild guild, + GenericEvent event) { String durationMessage = temporaryData == null ? "permanently" : "for " + temporaryData.duration(); String dmMessage = @@ -88,9 +89,9 @@ private static RestAction sendDm(@NotNull ISnowflake target, .map(Result::isSuccess); } - private static @NotNull MessageEmbed sendFeedback(boolean hasSentDm, @NotNull Member target, - @NotNull Member author, @Nullable ModerationUtils.TemporaryData temporaryData, - @NotNull String reason) { + @Nonnull + private static MessageEmbed sendFeedback(boolean hasSentDm, Member target, Member author, + @Nullable ModerationUtils.TemporaryData temporaryData, String reason) { String durationText = "The mute duration is: " + (temporaryData == null ? "permanent" : temporaryData.duration()); String dmNoticeText = ""; @@ -101,9 +102,9 @@ private static RestAction sendDm(@NotNull ISnowflake target, target.getUser(), durationText + dmNoticeText, reason); } - private AuditableRestAction muteUser(@NotNull Member target, @NotNull Member author, - @Nullable ModerationUtils.TemporaryData temporaryData, @NotNull String reason, - @NotNull Guild guild) { + @Nonnull + private AuditableRestAction muteUser(Member target, Member author, + @Nullable ModerationUtils.TemporaryData temporaryData, String reason, Guild guild) { String durationMessage = temporaryData == null ? "permanently" : "for " + temporaryData.duration(); logger.info("'{}' ({}) muted the user '{}' ({}) {} in guild '{}' for reason '{}'.", @@ -119,10 +120,9 @@ private AuditableRestAction muteUser(@NotNull Member target, @NotNull Memb .reason(reason); } - @SuppressWarnings("MethodWithTooManyParameters") - private void muteUserFlow(@NotNull Member target, @NotNull Member author, - @Nullable ModerationUtils.TemporaryData temporaryData, @NotNull String reason, - @NotNull Guild guild, @NotNull SlashCommandInteractionEvent event) { + private void muteUserFlow(Member target, Member author, + @Nullable ModerationUtils.TemporaryData temporaryData, String reason, Guild guild, + SlashCommandInteractionEvent event) { sendDm(target, temporaryData, reason, guild, event) .flatMap(hasSentDm -> muteUser(target, author, temporaryData, reason, guild) .map(result -> hasSentDm)) @@ -131,10 +131,8 @@ private void muteUserFlow(@NotNull Member target, @NotNull Member author, .queue(); } - @SuppressWarnings({"BooleanMethodNameMustStartWithQuestion", "MethodWithTooManyParameters"}) - private boolean handleChecks(@NotNull Member bot, @NotNull Member author, - @Nullable Member target, @NotNull CharSequence reason, @NotNull Guild guild, - @NotNull IReplyCallback event) { + private boolean handleChecks(Member bot, Member author, @Nullable Member target, + CharSequence reason, Guild guild, IReplyCallback event) { if (!ModerationUtils.handleRoleChangeChecks( ModerationUtils.getMutedRole(guild, config).orElse(null), ACTION_VERB, target, bot, author, guild, reason, event)) { @@ -152,7 +150,7 @@ private boolean handleChecks(@NotNull Member bot, @NotNull Member author, } @Override - public void onSlashCommand(@NotNull SlashCommandInteractionEvent event) { + public void onSlashCommand(SlashCommandInteractionEvent event) { Member target = Objects.requireNonNull(event.getOption(TARGET_OPTION), "The target is null") .getAsMember(); Member author = Objects.requireNonNull(event.getMember(), "The author is null"); diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/NoteCommand.java b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/NoteCommand.java index 54bcc11426..3d0cc9cf2e 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/NoteCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/NoteCommand.java @@ -5,13 +5,12 @@ import net.dv8tion.jda.api.interactions.callbacks.IReplyCallback; import net.dv8tion.jda.api.interactions.commands.OptionMapping; import net.dv8tion.jda.api.interactions.commands.OptionType; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.togetherjava.tjbot.commands.SlashCommandAdapter; import org.togetherjava.tjbot.commands.SlashCommandVisibility; +import javax.annotation.Nullable; import java.util.Objects; /** @@ -34,7 +33,7 @@ public final class NoteCommand extends SlashCommandAdapter { * * @param actionsStore used to store actions issued by this command */ - public NoteCommand(@NotNull ModerationActionsStore actionsStore) { + public NoteCommand(ModerationActionsStore actionsStore) { super("note", "Writes a note about the given user", SlashCommandVisibility.GUILD); getData() @@ -47,7 +46,7 @@ public NoteCommand(@NotNull ModerationActionsStore actionsStore) { } @Override - public void onSlashCommand(@NotNull SlashCommandInteractionEvent event) { + public void onSlashCommand(SlashCommandInteractionEvent event) { OptionMapping targetOption = event.getOption(USER_OPTION); Member author = event.getMember(); Guild guild = event.getGuild(); @@ -61,9 +60,8 @@ public void onSlashCommand(@NotNull SlashCommandInteractionEvent event) { sendNote(targetOption.getAsUser(), author, content, guild, event); } - @SuppressWarnings("BooleanMethodNameMustStartWithQuestion") - private boolean handleChecks(@NotNull Member bot, @NotNull Member author, - @Nullable Member target, CharSequence content, @NotNull IReplyCallback event) { + private boolean handleChecks(Member bot, Member author, @Nullable Member target, + CharSequence content, IReplyCallback event) { if (target != null && !ModerationUtils.handleCanInteractWithTarget(ACTION_VERB, bot, author, target, event)) { return false; @@ -72,14 +70,13 @@ private boolean handleChecks(@NotNull Member bot, @NotNull Member author, return ModerationUtils.handleReason(content, event); } - private void sendNote(@NotNull User target, @NotNull Member author, @NotNull String content, - @NotNull ISnowflake guild, @NotNull IReplyCallback event) { + private void sendNote(User target, Member author, String content, ISnowflake guild, + IReplyCallback event) { storeNote(target, author, content, guild); sendFeedback(target, author, content, event); } - private void storeNote(@NotNull User target, @NotNull Member author, @NotNull String content, - @NotNull ISnowflake guild) { + private void storeNote(User target, Member author, String content, ISnowflake guild) { logger.info("'{}' ({}) wrote a note about the user '{}' ({}) with content '{}'.", author.getUser().getAsTag(), author.getId(), target.getAsTag(), target.getId(), content); @@ -88,8 +85,8 @@ private void storeNote(@NotNull User target, @NotNull Member author, @NotNull St ModerationAction.NOTE, null, content); } - private static void sendFeedback(@NotNull User target, @NotNull Member author, - @NotNull String noteContent, @NotNull IReplyCallback event) { + private static void sendFeedback(User target, Member author, String noteContent, + IReplyCallback event) { MessageEmbed feedback = ModerationUtils.createActionResponse(author.getUser(), ModerationAction.NOTE, target, null, noteContent); diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/QuarantineCommand.java b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/QuarantineCommand.java index 3e845c643a..73091d3c2e 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/QuarantineCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/QuarantineCommand.java @@ -8,14 +8,14 @@ import net.dv8tion.jda.api.requests.RestAction; import net.dv8tion.jda.api.requests.restaction.AuditableRestAction; import net.dv8tion.jda.api.utils.Result; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.togetherjava.tjbot.commands.SlashCommandAdapter; import org.togetherjava.tjbot.commands.SlashCommandVisibility; import org.togetherjava.tjbot.config.Config; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; import java.util.Objects; /** @@ -41,7 +41,7 @@ public final class QuarantineCommand extends SlashCommandAdapter { * @param actionsStore used to store actions issued by this command * @param config the config to use for this */ - public QuarantineCommand(@NotNull ModerationActionsStore actionsStore, @NotNull Config config) { + public QuarantineCommand(ModerationActionsStore actionsStore, Config config) { super(COMMAND_NAME, "Puts the given user under quarantine. They can not interact with anyone anymore then.", SlashCommandVisibility.GUILD); @@ -55,12 +55,13 @@ public QuarantineCommand(@NotNull ModerationActionsStore actionsStore, @NotNull this.actionsStore = Objects.requireNonNull(actionsStore); } - private static void handleAlreadyQuarantinedTarget(@NotNull IReplyCallback event) { + private static void handleAlreadyQuarantinedTarget(IReplyCallback event) { event.reply("The user is already quarantined.").setEphemeral(true).queue(); } - private static RestAction sendDm(@NotNull ISnowflake target, @NotNull String reason, - @NotNull Guild guild, @NotNull GenericEvent event) { + @Nonnull + private static RestAction sendDm(ISnowflake target, String reason, Guild guild, + GenericEvent event) { String dmMessage = """ Hey there, sorry to tell you but unfortunately you have been put under quarantine in the server %s. @@ -77,8 +78,9 @@ private static RestAction sendDm(@NotNull ISnowflake target, @NotNull S .map(Result::isSuccess); } - private static @NotNull MessageEmbed sendFeedback(boolean hasSentDm, @NotNull Member target, - @NotNull Member author, @NotNull String reason) { + @Nonnull + private static MessageEmbed sendFeedback(boolean hasSentDm, Member target, Member author, + String reason) { String dmNoticeText = ""; if (!hasSentDm) { dmNoticeText = "\n(Unable to send them a DM.)"; @@ -87,8 +89,9 @@ private static RestAction sendDm(@NotNull ISnowflake target, @NotNull S target.getUser(), dmNoticeText, reason); } - private AuditableRestAction quarantineUser(@NotNull Member target, @NotNull Member author, - @NotNull String reason, @NotNull Guild guild) { + @Nonnull + private AuditableRestAction quarantineUser(Member target, Member author, String reason, + Guild guild) { logger.info("'{}' ({}) quarantined the user '{}' ({}) in guild '{}' for reason '{}'.", author.getUser().getAsTag(), author.getId(), target.getUser().getAsTag(), target.getId(), guild.getName(), reason); @@ -102,9 +105,8 @@ private AuditableRestAction quarantineUser(@NotNull Member target, @NotNul .reason(reason); } - private void quarantineUserFlow(@NotNull Member target, @NotNull Member author, - @NotNull String reason, @NotNull Guild guild, - @NotNull SlashCommandInteractionEvent event) { + private void quarantineUserFlow(Member target, Member author, String reason, Guild guild, + SlashCommandInteractionEvent event) { sendDm(target, reason, guild, event) .flatMap(hasSentDm -> quarantineUser(target, author, reason, guild) .map(result -> hasSentDm)) @@ -113,10 +115,8 @@ private void quarantineUserFlow(@NotNull Member target, @NotNull Member author, .queue(); } - @SuppressWarnings({"BooleanMethodNameMustStartWithQuestion", "MethodWithTooManyParameters"}) - private boolean handleChecks(@NotNull Member bot, @NotNull Member author, - @Nullable Member target, @NotNull CharSequence reason, @NotNull Guild guild, - @NotNull IReplyCallback event) { + private boolean handleChecks(Member bot, Member author, @Nullable Member target, + CharSequence reason, Guild guild, IReplyCallback event) { if (!ModerationUtils.handleRoleChangeChecks( ModerationUtils.getQuarantinedRole(guild, config).orElse(null), ACTION_VERB, target, bot, author, guild, reason, event)) { @@ -136,7 +136,7 @@ private boolean handleChecks(@NotNull Member bot, @NotNull Member author, } @Override - public void onSlashCommand(@NotNull SlashCommandInteractionEvent event) { + public void onSlashCommand(SlashCommandInteractionEvent event) { Member target = event.getOption(TARGET_OPTION).getAsMember(); Member author = event.getMember(); String reason = event.getOption(REASON_OPTION).getAsString(); diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/RejoinModerationRoleListener.java b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/RejoinModerationRoleListener.java index 2d306f0247..0df83b86d3 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/RejoinModerationRoleListener.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/RejoinModerationRoleListener.java @@ -6,7 +6,6 @@ import net.dv8tion.jda.api.entities.Role; import net.dv8tion.jda.api.events.GenericEvent; import net.dv8tion.jda.api.events.guild.member.GuildMemberJoinEvent; -import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.togetherjava.tjbot.commands.EventReceiver; @@ -39,8 +38,7 @@ public final class RejoinModerationRoleListener implements EventReceiver { * user should be e.g. muted * @param config the config to use for this */ - public RejoinModerationRoleListener(@NotNull ModerationActionsStore actionsStore, - @NotNull Config config) { + public RejoinModerationRoleListener(ModerationActionsStore actionsStore, Config config) { this.actionsStore = actionsStore; moderationRoles = List.of( @@ -52,13 +50,13 @@ public RejoinModerationRoleListener(@NotNull ModerationActionsStore actionsStore } @Override - public void onEvent(@NotNull GenericEvent event) { + public void onEvent(GenericEvent event) { if (event instanceof GuildMemberJoinEvent joinEvent) { onGuildMemberJoin(joinEvent); } } - private void onGuildMemberJoin(@NotNull GuildMemberJoinEvent event) { + private void onGuildMemberJoin(GuildMemberJoinEvent event) { Member member = event.getMember(); for (ModerationRole moderationRole : moderationRoles) { @@ -68,8 +66,8 @@ private void onGuildMemberJoin(@NotNull GuildMemberJoinEvent event) { } } - private boolean shouldApplyModerationRole(@NotNull ModerationRole moderationRole, - @NotNull IPermissionHolder member) { + private boolean shouldApplyModerationRole(ModerationRole moderationRole, + IPermissionHolder member) { Optional lastApplyAction = actionsStore.findLastActionAgainstTargetByType( member.getGuild().getIdLong(), member.getIdLong(), moderationRole.applyAction); if (lastApplyAction.isEmpty()) { @@ -93,8 +91,7 @@ private boolean shouldApplyModerationRole(@NotNull ModerationRole moderationRole return false; } - private static void applyModerationRole(@NotNull ModerationRole moderationRole, - @NotNull Member member) { + private static void applyModerationRole(ModerationRole moderationRole, Member member) { Guild guild = member.getGuild(); logger.info("Reapplied existing {} to user '{}' ({}) in guild '{}' after rejoining.", moderationRole.actionName, member.getUser().getAsTag(), member.getId(), @@ -106,7 +103,7 @@ private static void applyModerationRole(@NotNull ModerationRole moderationRole, .queue(); } - private record ModerationRole(@NotNull String actionName, @NotNull ModerationAction applyAction, - @NotNull ModerationAction revokeAction, @NotNull Function guildToRole) { + private record ModerationRole(String actionName, ModerationAction applyAction, + ModerationAction revokeAction, Function guildToRole) { } } diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/UnbanCommand.java b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/UnbanCommand.java index 654ac014e9..e9a6ad2bd6 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/UnbanCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/UnbanCommand.java @@ -7,7 +7,6 @@ import net.dv8tion.jda.api.interactions.callbacks.IReplyCallback; import net.dv8tion.jda.api.interactions.commands.OptionType; import net.dv8tion.jda.api.requests.ErrorResponse; -import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.togetherjava.tjbot.commands.SlashCommandAdapter; @@ -32,7 +31,7 @@ public final class UnbanCommand extends SlashCommandAdapter { * * @param actionsStore used to store actions issued by this command */ - public UnbanCommand(@NotNull ModerationActionsStore actionsStore) { + public UnbanCommand(ModerationActionsStore actionsStore) { super(COMMAND_NAME, "Unbans the given user from the server", SlashCommandVisibility.GUILD); getData() @@ -43,8 +42,8 @@ public UnbanCommand(@NotNull ModerationActionsStore actionsStore) { this.actionsStore = Objects.requireNonNull(actionsStore); } - private void unban(@NotNull User target, @NotNull Member author, @NotNull String reason, - @NotNull Guild guild, @NotNull IReplyCallback event) { + private void unban(User target, Member author, String reason, Guild guild, + IReplyCallback event) { guild.unban(target).reason(reason).queue(result -> { MessageEmbed message = ModerationUtils.createActionResponse(author.getUser(), ModerationAction.UNBAN, target, null, reason); @@ -59,8 +58,7 @@ private void unban(@NotNull User target, @NotNull Member author, @NotNull String }, unbanFailure -> handleFailure(unbanFailure, target, event)); } - private static void handleFailure(@NotNull Throwable unbanFailure, @NotNull User target, - @NotNull IReplyCallback event) { + private static void handleFailure(Throwable unbanFailure, User target, IReplyCallback event) { String targetTag = target.getAsTag(); if (unbanFailure instanceof ErrorResponseException errorResponseException) { if (errorResponseException.getErrorResponse() == ErrorResponse.UNKNOWN_USER) { @@ -82,15 +80,14 @@ private static void handleFailure(@NotNull Throwable unbanFailure, @NotNull User targetTag, unbanFailure); } - @SuppressWarnings({"BooleanMethodNameMustStartWithQuestion"}) - private boolean handleChecks(@NotNull IPermissionHolder bot, @NotNull Member author, - @NotNull CharSequence reason, @NotNull Guild guild, @NotNull IReplyCallback event) { + private boolean handleChecks(IPermissionHolder bot, Member author, CharSequence reason, + Guild guild, IReplyCallback event) { if (!ModerationUtils.handleHasBotPermissions(ACTION_VERB, Permission.BAN_MEMBERS, bot, guild, event)) { return false; } if (!ModerationUtils.handleHasAuthorPermissions(ACTION_VERB, Permission.BAN_MEMBERS, author, - guild, event)) { + event)) { return false; } @@ -98,7 +95,7 @@ private boolean handleChecks(@NotNull IPermissionHolder bot, @NotNull Member aut } @Override - public void onSlashCommand(@NotNull SlashCommandInteractionEvent event) { + public void onSlashCommand(SlashCommandInteractionEvent event) { User target = Objects.requireNonNull(event.getOption(TARGET_OPTION), "The target is null") .getAsUser(); Member author = Objects.requireNonNull(event.getMember(), "The author is null"); diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/UnmuteCommand.java b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/UnmuteCommand.java index d2b3041660..f0414df212 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/UnmuteCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/UnmuteCommand.java @@ -8,14 +8,14 @@ import net.dv8tion.jda.api.requests.RestAction; import net.dv8tion.jda.api.requests.restaction.AuditableRestAction; import net.dv8tion.jda.api.utils.Result; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.togetherjava.tjbot.commands.SlashCommandAdapter; import org.togetherjava.tjbot.commands.SlashCommandVisibility; import org.togetherjava.tjbot.config.Config; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; import java.util.Objects; /** @@ -40,7 +40,7 @@ public final class UnmuteCommand extends SlashCommandAdapter { * @param actionsStore used to store actions issued by this command * @param config the config to use for this */ - public UnmuteCommand(@NotNull ModerationActionsStore actionsStore, @NotNull Config config) { + public UnmuteCommand(ModerationActionsStore actionsStore, Config config) { super(COMMAND_NAME, "Unmutes the given already muted user so that they can send messages again", SlashCommandVisibility.GUILD); @@ -52,12 +52,13 @@ public UnmuteCommand(@NotNull ModerationActionsStore actionsStore, @NotNull Conf this.actionsStore = Objects.requireNonNull(actionsStore); } - private static void handleNotMutedTarget(@NotNull IReplyCallback event) { + private static void handleNotMutedTarget(IReplyCallback event) { event.reply("The user is not muted.").setEphemeral(true).queue(); } - private static RestAction sendDm(@NotNull ISnowflake target, @NotNull String reason, - @NotNull Guild guild, @NotNull GenericEvent event) { + @Nonnull + private static RestAction sendDm(ISnowflake target, String reason, Guild guild, + GenericEvent event) { String dmMessage = """ Hey there, you have been unmuted in the server %s. This means you can now send messages in the server again. @@ -70,8 +71,9 @@ private static RestAction sendDm(@NotNull ISnowflake target, @NotNull S .map(Result::isSuccess); } - private static @NotNull MessageEmbed sendFeedback(boolean hasSentDm, @NotNull Member target, - @NotNull Member author, @NotNull String reason) { + @Nonnull + private static MessageEmbed sendFeedback(boolean hasSentDm, Member target, Member author, + String reason) { String dmNoticeText = ""; if (!hasSentDm) { dmNoticeText = "(Unable to send them a DM.)"; @@ -80,8 +82,9 @@ private static RestAction sendDm(@NotNull ISnowflake target, @NotNull S target.getUser(), dmNoticeText, reason); } - private AuditableRestAction unmuteUser(@NotNull Member target, @NotNull Member author, - @NotNull String reason, @NotNull Guild guild) { + @Nonnull + private AuditableRestAction unmuteUser(Member target, Member author, String reason, + Guild guild) { logger.info("'{}' ({}) unmuted the user '{}' ({}) in guild '{}' for reason '{}'.", author.getUser().getAsTag(), author.getId(), target.getUser().getAsTag(), target.getId(), guild.getName(), reason); @@ -94,9 +97,8 @@ private AuditableRestAction unmuteUser(@NotNull Member target, @NotNull Me .reason(reason); } - private void unmuteUserFlow(@NotNull Member target, @NotNull Member author, - @NotNull String reason, @NotNull Guild guild, - @NotNull SlashCommandInteractionEvent event) { + private void unmuteUserFlow(Member target, Member author, String reason, Guild guild, + SlashCommandInteractionEvent event) { sendDm(target, reason, guild, event) .flatMap( hasSentDm -> unmuteUser(target, author, reason, guild).map(result -> hasSentDm)) @@ -105,10 +107,8 @@ private void unmuteUserFlow(@NotNull Member target, @NotNull Member author, .queue(); } - @SuppressWarnings({"BooleanMethodNameMustStartWithQuestion", "MethodWithTooManyParameters"}) - private boolean handleChecks(@NotNull Member bot, @NotNull Member author, - @Nullable Member target, @NotNull CharSequence reason, @NotNull Guild guild, - @NotNull IReplyCallback event) { + private boolean handleChecks(Member bot, Member author, @Nullable Member target, + CharSequence reason, Guild guild, IReplyCallback event) { if (!ModerationUtils.handleRoleChangeChecks( ModerationUtils.getMutedRole(guild, config).orElse(null), ACTION_VERB, target, bot, author, guild, reason, event)) { @@ -126,7 +126,7 @@ private boolean handleChecks(@NotNull Member bot, @NotNull Member author, } @Override - public void onSlashCommand(@NotNull SlashCommandInteractionEvent event) { + public void onSlashCommand(SlashCommandInteractionEvent event) { Member target = Objects.requireNonNull(event.getOption(TARGET_OPTION), "The target is null") .getAsMember(); Member author = Objects.requireNonNull(event.getMember(), "The author is null"); diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/UnquarantineCommand.java b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/UnquarantineCommand.java index c04b173f96..117d61d5ce 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/UnquarantineCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/UnquarantineCommand.java @@ -8,14 +8,14 @@ import net.dv8tion.jda.api.requests.RestAction; import net.dv8tion.jda.api.requests.restaction.AuditableRestAction; import net.dv8tion.jda.api.utils.Result; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.togetherjava.tjbot.commands.SlashCommandAdapter; import org.togetherjava.tjbot.commands.SlashCommandVisibility; import org.togetherjava.tjbot.config.Config; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; import java.util.Objects; /** @@ -41,8 +41,7 @@ public final class UnquarantineCommand extends SlashCommandAdapter { * @param actionsStore used to store actions issued by this command * @param config the config to use for this */ - public UnquarantineCommand(@NotNull ModerationActionsStore actionsStore, - @NotNull Config config) { + public UnquarantineCommand(ModerationActionsStore actionsStore, Config config) { super(COMMAND_NAME, "Unquarantines the given already quarantined user so that they can interact again", SlashCommandVisibility.GUILD); @@ -57,12 +56,13 @@ public UnquarantineCommand(@NotNull ModerationActionsStore actionsStore, this.actionsStore = Objects.requireNonNull(actionsStore); } - private static void handleNotQuarantinedTarget(@NotNull IReplyCallback event) { + private static void handleNotQuarantinedTarget(IReplyCallback event) { event.reply("The user is not quarantined.").setEphemeral(true).queue(); } - private static RestAction sendDm(@NotNull ISnowflake target, @NotNull String reason, - @NotNull Guild guild, @NotNull GenericEvent event) { + @Nonnull + private static RestAction sendDm(ISnowflake target, String reason, Guild guild, + GenericEvent event) { String dmMessage = """ Hey there, you have been put out of quarantine in the server %s. This means you can now interact with others in the server again. @@ -76,8 +76,9 @@ private static RestAction sendDm(@NotNull ISnowflake target, @NotNull S .map(Result::isSuccess); } - private static @NotNull MessageEmbed sendFeedback(boolean hasSentDm, @NotNull Member target, - @NotNull Member author, @NotNull String reason) { + @Nonnull + private static MessageEmbed sendFeedback(boolean hasSentDm, Member target, Member author, + String reason) { String dmNoticeText = ""; if (!hasSentDm) { dmNoticeText = "(Unable to send them a DM.)"; @@ -86,8 +87,9 @@ private static RestAction sendDm(@NotNull ISnowflake target, @NotNull S target.getUser(), dmNoticeText, reason); } - private AuditableRestAction unquarantineUser(@NotNull Member target, - @NotNull Member author, @NotNull String reason, @NotNull Guild guild) { + @Nonnull + private AuditableRestAction unquarantineUser(Member target, Member author, String reason, + Guild guild) { logger.info("'{}' ({}) unquarantined the user '{}' ({}) in guild '{}' for reason '{}'.", author.getUser().getAsTag(), author.getId(), target.getUser().getAsTag(), target.getId(), guild.getName(), reason); @@ -101,9 +103,8 @@ private AuditableRestAction unquarantineUser(@NotNull Member target, .reason(reason); } - private void unquarantineUserFlow(@NotNull Member target, @NotNull Member author, - @NotNull String reason, @NotNull Guild guild, - @NotNull SlashCommandInteractionEvent event) { + private void unquarantineUserFlow(Member target, Member author, String reason, Guild guild, + SlashCommandInteractionEvent event) { sendDm(target, reason, guild, event) .flatMap(hasSentDm -> unquarantineUser(target, author, reason, guild) .map(result -> hasSentDm)) @@ -112,10 +113,8 @@ private void unquarantineUserFlow(@NotNull Member target, @NotNull Member author .queue(); } - @SuppressWarnings({"BooleanMethodNameMustStartWithQuestion", "MethodWithTooManyParameters"}) - private boolean handleChecks(@NotNull Member bot, @NotNull Member author, - @Nullable Member target, @NotNull CharSequence reason, @NotNull Guild guild, - @NotNull IReplyCallback event) { + private boolean handleChecks(Member bot, Member author, @Nullable Member target, + CharSequence reason, Guild guild, IReplyCallback event) { if (!ModerationUtils.handleRoleChangeChecks( ModerationUtils.getQuarantinedRole(guild, config).orElse(null), ACTION_VERB, target, bot, author, guild, reason, event)) { @@ -135,7 +134,7 @@ private boolean handleChecks(@NotNull Member bot, @NotNull Member author, } @Override - public void onSlashCommand(@NotNull SlashCommandInteractionEvent event) { + public void onSlashCommand(SlashCommandInteractionEvent event) { Member target = event.getOption(TARGET_OPTION).getAsMember(); Member author = event.getMember(); String reason = event.getOption(REASON_OPTION).getAsString(); diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/WarnCommand.java b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/WarnCommand.java index a29b1ea51d..a852a56728 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/WarnCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/WarnCommand.java @@ -7,13 +7,13 @@ import net.dv8tion.jda.api.interactions.commands.OptionType; import net.dv8tion.jda.api.requests.RestAction; import net.dv8tion.jda.api.utils.Result; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.togetherjava.tjbot.commands.SlashCommandAdapter; import org.togetherjava.tjbot.commands.SlashCommandVisibility; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; import java.util.Objects; /** @@ -35,7 +35,7 @@ public final class WarnCommand extends SlashCommandAdapter { * * @param actionsStore used to store actions issued by this command */ - public WarnCommand(@NotNull ModerationActionsStore actionsStore) { + public WarnCommand(ModerationActionsStore actionsStore) { super("warn", "Warns the given user", SlashCommandVisibility.GUILD); getData().addOption(OptionType.USER, USER_OPTION, "The user who you want to warn", true) @@ -44,9 +44,9 @@ public WarnCommand(@NotNull ModerationActionsStore actionsStore) { this.actionsStore = Objects.requireNonNull(actionsStore); } - private @NotNull RestAction warnUserFlow(@NotNull User target, - @NotNull Member author, @NotNull String reason, @NotNull Guild guild, - @NotNull SlashCommandInteractionEvent event) { + @Nonnull + private RestAction warnUserFlow(User target, Member author, String reason, + Guild guild, SlashCommandInteractionEvent event) { return dmUser(target, reason, guild, event).map(hasSentDm -> { warnUser(target, author, reason, guild); return hasSentDm; @@ -55,9 +55,9 @@ public WarnCommand(@NotNull ModerationActionsStore actionsStore) { .flatMap(event::replyEmbeds); } - private static @NotNull RestAction dmUser(@NotNull ISnowflake target, - @NotNull String reason, @NotNull Guild guild, - @NotNull SlashCommandInteractionEvent event) { + @Nonnull + private static RestAction dmUser(ISnowflake target, String reason, Guild guild, + SlashCommandInteractionEvent event) { return event.getJDA() .openPrivateChannelById(target.getId()) .flatMap(channel -> channel.sendMessage( @@ -71,8 +71,7 @@ public WarnCommand(@NotNull ModerationActionsStore actionsStore) { .map(Result::isSuccess); } - private void warnUser(@NotNull User target, @NotNull Member author, @NotNull String reason, - @NotNull Guild guild) { + private void warnUser(User target, Member author, String reason, Guild guild) { logger.info("'{}' ({}) warned the user '{}' ({}) for reason '{}'.", author.getUser().getAsTag(), author.getId(), target.getAsTag(), target.getId(), reason); @@ -81,8 +80,9 @@ private void warnUser(@NotNull User target, @NotNull Member author, @NotNull Str ModerationAction.WARN, null, reason); } - private static @NotNull MessageEmbed sendFeedback(boolean hasSentDm, @NotNull User target, - @NotNull Member author, @NotNull String reason) { + @Nonnull + private static MessageEmbed sendFeedback(boolean hasSentDm, User target, Member author, + String reason) { String dmNoticeText = ""; if (!hasSentDm) { dmNoticeText = "(Unable to send them a DM.)"; @@ -92,7 +92,7 @@ private void warnUser(@NotNull User target, @NotNull Member author, @NotNull Str } @Override - public void onSlashCommand(@NotNull SlashCommandInteractionEvent event) { + public void onSlashCommand(SlashCommandInteractionEvent event) { OptionMapping targetOption = Objects.requireNonNull(event.getOption(USER_OPTION), "The target is null"); Member author = Objects.requireNonNull(event.getMember(), "The author is null"); @@ -108,9 +108,8 @@ public void onSlashCommand(@NotNull SlashCommandInteractionEvent event) { warnUserFlow(targetOption.getAsUser(), author, reason, guild, event).queue(); } - @SuppressWarnings("BooleanMethodNameMustStartWithQuestion") - private boolean handleChecks(@NotNull Member bot, @NotNull Member author, - @Nullable Member target, String reason, @NotNull SlashCommandInteractionEvent event) { + private boolean handleChecks(Member bot, Member author, @Nullable Member target, String reason, + SlashCommandInteractionEvent event) { if (target != null && !ModerationUtils.handleCanInteractWithTarget(ACTION_VERB, bot, author, target, event)) { return false; diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/WhoIsCommand.java b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/WhoIsCommand.java index 4403353880..c54cefb97e 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/WhoIsCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/WhoIsCommand.java @@ -8,12 +8,12 @@ import net.dv8tion.jda.api.interactions.commands.OptionMapping; import net.dv8tion.jda.api.interactions.commands.OptionType; import net.dv8tion.jda.api.requests.restaction.interactions.ReplyCallbackAction; -import org.jetbrains.annotations.NotNull; import org.togetherjava.tjbot.commands.SlashCommandAdapter; import org.togetherjava.tjbot.commands.SlashCommandVisibility; import org.togetherjava.tjbot.commands.utils.DiscordClientAction; import javax.annotation.CheckReturnValue; +import javax.annotation.Nonnull; import java.awt.Color; import java.time.Instant; import java.time.OffsetDateTime; @@ -49,7 +49,7 @@ public WhoIsCommand() { } @Override - public void onSlashCommand(@NotNull final SlashCommandInteractionEvent event) { + public void onSlashCommand(final SlashCommandInteractionEvent event) { OptionMapping userOption = Objects.requireNonNull(event.getOption(USER_OPTION), "The given user option cannot be null"); OptionMapping showServerSpecificInfoOption = event.getOption(SHOW_SERVER_INFO_OPTION); @@ -70,8 +70,9 @@ public void onSlashCommand(@NotNull final SlashCommandInteractionEvent event) { } @CheckReturnValue - private static @NotNull ReplyCallbackAction handleWhoIsUser(final @NotNull IReplyCallback event, - final @NotNull User user, final @NotNull User.Profile profile) { + @Nonnull + private static ReplyCallbackAction handleWhoIsUser(final IReplyCallback event, final User user, + final User.Profile profile) { String description = userIdentificationToStringItem(user) + "\n**Is bot:** " + user.isBot() + userFlagsToStringItem(user.getFlags()) + "\n**Registration date:** " + DATE_TIME_FORMAT.format(user.getTimeCreated()); @@ -85,9 +86,9 @@ public void onSlashCommand(@NotNull final SlashCommandInteractionEvent event) { } @CheckReturnValue - private static @NotNull ReplyCallbackAction handleWhoIsMember( - final @NotNull IReplyCallback event, final @NotNull Member member, - final @NotNull User.Profile profile) { + @Nonnull + private static ReplyCallbackAction handleWhoIsMember(final IReplyCallback event, + final Member member, final User.Profile profile) { User user = member.getUser(); Color memberColor = member.getColor(); @@ -108,15 +109,16 @@ public void onSlashCommand(@NotNull final SlashCommandInteractionEvent event) { return sendEmbedWithProfileAction(event, embedBuilder.build(), user.getId()); } - private static @NotNull ReplyCallbackAction sendEmbedWithProfileAction( - final @NotNull IReplyCallback event, @NotNull MessageEmbed embed, - @NotNull String userId) { + @Nonnull + private static ReplyCallbackAction sendEmbedWithProfileAction(final IReplyCallback event, + MessageEmbed embed, String userId) { return event.replyEmbeds(embed) .addActionRow( DiscordClientAction.General.USER.asLinkButton("Click to see profile!", userId)); } - private static @NotNull String voiceStateToStringItem(@NotNull final Member member) { + @Nonnull + private static String voiceStateToStringItem(final Member member) { GuildVoiceState voiceState = Objects.requireNonNull(member.getVoiceState(), "The given voiceState cannot be null"); @@ -137,9 +139,9 @@ public void onSlashCommand(@NotNull final SlashCommandInteractionEvent event) { * @param effectiveColor the {@link Color} that the embed will become * @return the generated {@link EmbedBuilder} */ - private static @NotNull EmbedBuilder generateEmbedBuilder(@NotNull final Interaction event, - @NotNull final User user, final @NotNull User.Profile profile, - final Color effectiveColor) { + @Nonnull + private static EmbedBuilder generateEmbedBuilder(final Interaction event, final User user, + final User.Profile profile, final Color effectiveColor) { EmbedBuilder embedBuilder = new EmbedBuilder().setThumbnail(user.getEffectiveAvatarUrl()) .setColor(effectiveColor) @@ -160,7 +162,8 @@ public void onSlashCommand(@NotNull final SlashCommandInteractionEvent event) { * @param member the {@link Member} to take the booster properties from * @return user readable {@link String} */ - private static @NotNull String possibleBoosterToStringItem(final @NotNull Member member) { + @Nonnull + private static String possibleBoosterToStringItem(final Member member) { OffsetDateTime timeBoosted = member.getTimeBoosted(); if (null == timeBoosted) { @@ -177,7 +180,8 @@ public void onSlashCommand(@NotNull final SlashCommandInteractionEvent event) { * @param user the {@link User} to take the identifiers from * @return user readable {@link String} */ - private static @NotNull String userIdentificationToStringItem(final @NotNull User user) { + @Nonnull + private static String userIdentificationToStringItem(final User user) { return "**Mention:** " + user.getAsMention() + "\n**Tag:** " + user.getAsTag() + "\n**ID:** " + user.getId(); } @@ -188,7 +192,8 @@ public void onSlashCommand(@NotNull final SlashCommandInteractionEvent event) { * @param member member to take the Roles from * @return user readable {@link String} of the roles */ - private static String formatRoles(final @NotNull Member member) { + @Nonnull + private static String formatRoles(final Member member) { return member.getRoles().stream().map(Role::getAsMention).collect(Collectors.joining(", ")); } @@ -199,8 +204,8 @@ private static String formatRoles(final @NotNull Member member) { * (recommend {@link java.util.EnumSet} * @return user readable {@link StringBuilder} */ - private static @NotNull StringBuilder userFlagsToStringItem( - final @NotNull Collection flags) { + @Nonnull + private static StringBuilder userFlagsToStringItem(final Collection flags) { String formattedFlags = formatUserFlags(flags); StringBuilder result = hypeSquadToStringItem(flags); @@ -218,8 +223,8 @@ private static String formatRoles(final @NotNull Member member) { * (recommend {@link java.util.EnumSet} * @return user readable {@link StringBuilder} */ - private static @NotNull StringBuilder hypeSquadToStringItem( - final @NotNull Collection flags) { + @Nonnull + private static StringBuilder hypeSquadToStringItem(final Collection flags) { StringBuilder stringBuilder = new StringBuilder("**\nHypesquad:** "); if (flags.contains(User.UserFlag.HYPESQUAD_BALANCE)) { @@ -242,8 +247,8 @@ private static String formatRoles(final @NotNull Member member) { * (recommend {@link java.util.EnumSet} * @return the user readable string */ - @NotNull - private static String formatUserFlags(final @NotNull Collection flags) { + @Nonnull + private static String formatUserFlags(final Collection flags) { return flags.stream() .map(User.UserFlag::getName) .filter(name -> (name.contains("Hypesquad"))) diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/package-info.java b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/package-info.java index 399fc1fa1f..779ef299a7 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/package-info.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/package-info.java @@ -2,4 +2,7 @@ * This package offers all the moderation commands from the application such as banning and kicking * users. */ +@ParametersAreNonnullByDefault package org.togetherjava.tjbot.commands.moderation; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/scam/ScamBlocker.java b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/scam/ScamBlocker.java index e4240cedb2..7c1e812d0d 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/scam/ScamBlocker.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/scam/ScamBlocker.java @@ -12,8 +12,6 @@ import net.dv8tion.jda.api.interactions.components.buttons.Button; import net.dv8tion.jda.api.interactions.components.buttons.ButtonStyle; import net.dv8tion.jda.api.requests.ErrorResponse; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.togetherjava.tjbot.commands.MessageReceiverAdapter; @@ -28,6 +26,8 @@ import org.togetherjava.tjbot.config.Config; import org.togetherjava.tjbot.config.ScamBlockerConfig; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; import java.awt.Color; import java.util.*; import java.util.function.Consumer; @@ -66,8 +66,8 @@ public final class ScamBlocker extends MessageReceiverAdapter implements UserInt * @param scamHistoryStore to store and retrieve scam history from * @param config the config to use for this */ - public ScamBlocker(@NotNull ModerationActionsStore actionsStore, - @NotNull ScamHistoryStore scamHistoryStore, @NotNull Config config) { + public ScamBlocker(ModerationActionsStore actionsStore, ScamHistoryStore scamHistoryStore, + Config config) { super(Pattern.compile(".*")); this.actionsStore = actionsStore; @@ -84,23 +84,23 @@ public ScamBlocker(@NotNull ModerationActionsStore actionsStore, } @Override - public @NotNull String getName() { + @Nonnull + public String getName() { return "scam-blocker"; } @Override - public void onSelectionMenu(@NotNull SelectMenuInteractionEvent event, - @NotNull List args) { + public void onSelectionMenu(SelectMenuInteractionEvent event, List args) { throw new UnsupportedOperationException("Not used"); } @Override - public void acceptComponentIdGenerator(@NotNull ComponentIdGenerator generator) { + public void acceptComponentIdGenerator(ComponentIdGenerator generator) { componentIdGenerator = generator; } @Override - public void onMessageReceived(@NotNull MessageReceivedEvent event) { + public void onMessageReceived(MessageReceivedEvent event) { if (event.getAuthor().isBot() || event.isWebhookMessage()) { return; } @@ -123,7 +123,7 @@ public void onMessageReceived(@NotNull MessageReceivedEvent event) { takeAction(event); } - private void takeActionWasAlreadyReported(@NotNull MessageReceivedEvent event) { + private void takeActionWasAlreadyReported(MessageReceivedEvent event) { // The user recently send the same scam already, and that was already reported and handled addScamToHistory(event); @@ -133,7 +133,7 @@ private void takeActionWasAlreadyReported(@NotNull MessageReceivedEvent event) { } } - private void takeAction(@NotNull MessageReceivedEvent event) { + private void takeAction(MessageReceivedEvent event) { switch (mode) { case OFF -> throw new AssertionError( "The OFF-mode should be detected earlier already to prevent expensive computation"); @@ -146,25 +146,25 @@ private void takeAction(@NotNull MessageReceivedEvent event) { } } - private void takeActionLogOnly(@NotNull MessageReceivedEvent event) { + private void takeActionLogOnly(MessageReceivedEvent event) { addScamToHistory(event); logScamMessage(event); } - private void takeActionApproveFirst(@NotNull MessageReceivedEvent event) { + private void takeActionApproveFirst(MessageReceivedEvent event) { addScamToHistory(event); logScamMessage(event); reportScamMessage(event, "Is this scam?", createConfirmDialog(event)); } - private void takeActionAutoDeleteButApproveQuarantine(@NotNull MessageReceivedEvent event) { + private void takeActionAutoDeleteButApproveQuarantine(MessageReceivedEvent event) { addScamToHistory(event); logScamMessage(event); deleteMessage(event); reportScamMessage(event, "Is this scam? (already deleted)", createConfirmDialog(event)); } - private void takeActionAutoDeleteAndQuarantine(@NotNull MessageReceivedEvent event) { + private void takeActionAutoDeleteAndQuarantine(MessageReceivedEvent event) { addScamToHistory(event); logScamMessage(event); deleteMessage(event); @@ -173,26 +173,25 @@ private void takeActionAutoDeleteAndQuarantine(@NotNull MessageReceivedEvent eve reportScamMessage(event, "Detected and handled scam", null); } - private void addScamToHistory(@NotNull MessageReceivedEvent event) { + private void addScamToHistory(MessageReceivedEvent event) { scamHistoryStore.addScam(event.getMessage(), MODES_WITH_IMMEDIATE_DELETION.contains(mode)); } - private void logScamMessage(@NotNull MessageReceivedEvent event) { + private void logScamMessage(MessageReceivedEvent event) { logger.warn("Detected a scam message ('{}') from user '{}' in channel '{}' of guild '{}'.", event.getMessageId(), event.getAuthor().getId(), event.getChannel().getId(), event.getGuild().getId()); } - private void deleteMessage(@NotNull MessageReceivedEvent event) { + private void deleteMessage(MessageReceivedEvent event) { event.getMessage().delete().queue(); } - private void quarantineAuthor(@NotNull MessageReceivedEvent event) { + private void quarantineAuthor(MessageReceivedEvent event) { quarantineAuthor(event.getGuild(), event.getMember(), event.getJDA().getSelfUser()); } - private void quarantineAuthor(@NotNull Guild guild, @NotNull Member author, - @NotNull SelfUser bot) { + private void quarantineAuthor(Guild guild, Member author, SelfUser bot) { String reason = "User posted scam that was automatically detected"; actionsStore.addAction(guild.getIdLong(), bot.getIdLong(), author.getIdLong(), @@ -205,7 +204,7 @@ private void quarantineAuthor(@NotNull Guild guild, @NotNull Member author, .queue(); } - private void reportScamMessage(@NotNull MessageReceivedEvent event, @NotNull String reportTitle, + private void reportScamMessage(MessageReceivedEvent event, String reportTitle, @Nullable ActionRow confirmDialog) { Guild guild = event.getGuild(); Optional reportChannel = getReportChannel(guild); @@ -231,11 +230,11 @@ private void reportScamMessage(@NotNull MessageReceivedEvent event, @NotNull Str reportChannel.orElseThrow().sendMessage(message).queue(); } - private void dmUser(@NotNull MessageReceivedEvent event) { + private void dmUser(MessageReceivedEvent event) { dmUser(event.getGuild(), event.getAuthor().getIdLong(), event.getJDA()); } - private void dmUser(@NotNull Guild guild, long userId, @NotNull JDA jda) { + private void dmUser(Guild guild, long userId, JDA jda) { String dmMessage = """ Hey there, we detected that you did send scam in the server %s and therefore put you under quarantine. @@ -251,11 +250,13 @@ If you think this was a mistake (for example, your account was hacked, but you g .queue(); } - private @NotNull Optional getReportChannel(@NotNull Guild guild) { + @Nonnull + private Optional getReportChannel(Guild guild) { return guild.getTextChannelCache().stream().filter(isReportChannel).findAny(); } - private @NotNull ActionRow createConfirmDialog(@NotNull MessageReceivedEvent event) { + @Nonnull + private ActionRow createConfirmDialog(MessageReceivedEvent event) { ComponentIdArguments args = new ComponentIdArguments(mode, event.getGuild().getIdLong(), event.getChannel().getIdLong(), event.getMessageIdLong(), event.getAuthor().getIdLong(), @@ -265,14 +266,14 @@ If you think this was a mistake (for example, your account was hacked, but you g Button.danger(generateComponentId(args), "No")); } - private @NotNull String generateComponentId(@NotNull ComponentIdArguments args) { + @Nonnull + private String generateComponentId(ComponentIdArguments args) { return Objects.requireNonNull(componentIdGenerator) .generate(new ComponentId(getName(), args.toList()), Lifespan.REGULAR); } @Override - public void onButtonClick(@NotNull ButtonInteractionEvent event, - @NotNull List argsRaw) { + public void onButtonClick(ButtonInteractionEvent event, List argsRaw) { ComponentIdArguments args = ComponentIdArguments.fromList(argsRaw); if (event.getMember().getRoles().stream().map(Role::getName).noneMatch(hasRequiredRole)) { event.reply( @@ -334,10 +335,11 @@ public void onButtonClick(@NotNull ButtonInteractionEvent event, } - private record ComponentIdArguments(@NotNull ScamBlockerConfig.Mode mode, long guildId, - long channelId, long messageId, long authorId, @NotNull String contentHash) { + private record ComponentIdArguments(ScamBlockerConfig.Mode mode, long guildId, long channelId, + long messageId, long authorId, String contentHash) { - static @NotNull ComponentIdArguments fromList(@NotNull List args) { + @Nonnull + static ComponentIdArguments fromList(List args) { ScamBlockerConfig.Mode mode = ScamBlockerConfig.Mode.valueOf(args.get(0)); long guildId = Long.parseLong(args.get(1)); long channelId = Long.parseLong(args.get(2)); @@ -348,7 +350,7 @@ private record ComponentIdArguments(@NotNull ScamBlockerConfig.Mode mode, long g contentHash); } - @NotNull + @Nonnull List toList() { return List.of(mode.name(), Long.toString(guildId), Long.toString(channelId), Long.toString(messageId), Long.toString(authorId), contentHash); diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/scam/ScamDetector.java b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/scam/ScamDetector.java index 15d32d15a9..d4a2281834 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/scam/ScamDetector.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/scam/ScamDetector.java @@ -1,6 +1,5 @@ package org.togetherjava.tjbot.commands.moderation.scam; -import org.jetbrains.annotations.NotNull; import org.togetherjava.tjbot.commands.utils.StringDistances; import org.togetherjava.tjbot.config.Config; import org.togetherjava.tjbot.config.ScamBlockerConfig; @@ -23,7 +22,7 @@ public final class ScamDetector { * * @param config the scam blocker config to use */ - public ScamDetector(@NotNull Config config) { + public ScamDetector(Config config) { this.config = config.getScamBlocker(); } @@ -33,20 +32,20 @@ public ScamDetector(@NotNull Config config) { * @param message the message to analyze * @return Whether the message classifies as scam */ - public boolean isScam(@NotNull CharSequence message) { + public boolean isScam(CharSequence message) { AnalyseResults results = new AnalyseResults(); TOKENIZER.splitAsStream(message).forEach(token -> analyzeToken(token, results)); return isScam(results); } - private boolean isScam(@NotNull AnalyseResults results) { + private boolean isScam(AnalyseResults results) { if (results.pingsEveryone && results.containsNitroKeyword && results.hasUrl) { return true; } return results.containsNitroKeyword && results.hasSuspiciousUrl; } - private void analyzeToken(@NotNull String token, @NotNull AnalyseResults results) { + private void analyzeToken(String token, AnalyseResults results) { if ("@everyone".equalsIgnoreCase(token)) { results.pingsEveryone = true; } @@ -60,7 +59,7 @@ private void analyzeToken(@NotNull String token, @NotNull AnalyseResults results } } - private void analyzeUrl(@NotNull String url, @NotNull AnalyseResults results) { + private void analyzeUrl(String url, AnalyseResults results) { String host; try { host = URI.create(url).getHost(); @@ -92,7 +91,7 @@ private void analyzeUrl(@NotNull String url, @NotNull AnalyseResults results) { } } - private boolean isHostSimilarToKeyword(@NotNull String host, @NotNull String keyword) { + private boolean isHostSimilarToKeyword(String host, String keyword) { // NOTE This algorithm is far from optimal. // It is good enough for our purpose though and not that complex. diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/scam/ScamHistoryPurgeRoutine.java b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/scam/ScamHistoryPurgeRoutine.java index 649f793b88..6c6ed71de9 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/scam/ScamHistoryPurgeRoutine.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/scam/ScamHistoryPurgeRoutine.java @@ -1,9 +1,9 @@ package org.togetherjava.tjbot.commands.moderation.scam; import net.dv8tion.jda.api.JDA; -import org.jetbrains.annotations.NotNull; import org.togetherjava.tjbot.commands.Routine; +import javax.annotation.Nonnull; import java.time.Instant; import java.time.Period; import java.util.concurrent.TimeUnit; @@ -20,17 +20,18 @@ public final class ScamHistoryPurgeRoutine implements Routine { * * @param scamHistoryStore containing the scam history to purge */ - public ScamHistoryPurgeRoutine(@NotNull ScamHistoryStore scamHistoryStore) { + public ScamHistoryPurgeRoutine(ScamHistoryStore scamHistoryStore) { this.scamHistoryStore = scamHistoryStore; } @Override - public @NotNull Schedule createSchedule() { + @Nonnull + public Schedule createSchedule() { return new Schedule(ScheduleMode.FIXED_RATE, 0, 1, TimeUnit.DAYS); } @Override - public void runRoutine(@NotNull JDA jda) { + public void runRoutine(JDA jda) { scamHistoryStore.deleteHistoryOlderThan(Instant.now().minus(DELETE_SCAM_RECORDS_AFTER)); } } diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/scam/ScamHistoryStore.java b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/scam/ScamHistoryStore.java index 3154820dc5..9f1802610b 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/scam/ScamHistoryStore.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/scam/ScamHistoryStore.java @@ -1,12 +1,12 @@ package org.togetherjava.tjbot.commands.moderation.scam; import net.dv8tion.jda.api.entities.Message; -import org.jetbrains.annotations.NotNull; import org.jooq.Result; import org.togetherjava.tjbot.commands.utils.Hashing; import org.togetherjava.tjbot.db.Database; import org.togetherjava.tjbot.db.generated.tables.records.ScamHistoryRecord; +import javax.annotation.Nonnull; import java.nio.charset.StandardCharsets; import java.time.Duration; import java.time.Instant; @@ -39,7 +39,7 @@ public final class ScamHistoryStore { * * @param database containing the scam history to work with */ - public ScamHistoryStore(@NotNull Database database) { + public ScamHistoryStore(Database database) { this.database = database; } @@ -49,7 +49,7 @@ public ScamHistoryStore(@NotNull Database database) { * @param scam the message to add * @param isDeleted whether the message is already, or about to get, deleted */ - public void addScam(@NotNull Message scam, boolean isDeleted) { + public void addScam(Message scam, boolean isDeleted) { Objects.requireNonNull(scam); database.write(context -> context.newRecord(SCAM_HISTORY) @@ -71,8 +71,8 @@ public void addScam(@NotNull Message scam, boolean isDeleted) { * @return identifications of all scam messages that have just been marked deleted, which * previously have not been marked accordingly yet */ - public @NotNull Collection markScamDuplicatesDeleted( - @NotNull Message scam) { + @Nonnull + public Collection markScamDuplicatesDeleted(Message scam) { return markScamDuplicatesDeleted(scam.getGuild().getIdLong(), scam.getAuthor().getIdLong(), hashMessageContent(scam)); } @@ -87,8 +87,9 @@ public void addScam(@NotNull Message scam, boolean isDeleted) { * @return identifications of all scam messages that have just been marked deleted, which * previously have not been marked accordingly yet */ - public @NotNull Collection markScamDuplicatesDeleted(long guildId, - long authorId, @NotNull String contentHash) { + @Nonnull + public Collection markScamDuplicatesDeleted(long guildId, long authorId, + String contentHash) { return database.writeAndProvide(context -> { Result undeletedDuplicates = context.selectFrom(SCAM_HISTORY) .where(SCAM_HISTORY.GUILD_ID.eq(guildId) @@ -111,7 +112,7 @@ public void addScam(@NotNull Message scam, boolean isDeleted) { * @param scam the scam message to look for duplicates * @return whether there are recent duplicates */ - public boolean hasRecentScamDuplicate(@NotNull Message scam) { + public boolean hasRecentScamDuplicate(Message scam) { Instant recentScamThreshold = Instant.now().minus(RECENT_SCAM_DURATION); return database.read(context -> context.fetchCount(SCAM_HISTORY, @@ -138,7 +139,8 @@ public void deleteHistoryOlderThan(Instant olderThan) { * @param message the message to hash * @return a text representation of the hash */ - public static @NotNull String hashMessageContent(@NotNull Message message) { + @Nonnull + public static String hashMessageContent(Message message) { return Hashing.bytesToHex(Hashing.hash(HASH_METHOD, message.getContentRaw().getBytes(StandardCharsets.UTF_8))); } @@ -154,8 +156,8 @@ public void deleteHistoryOlderThan(Instant olderThan) { */ public record ScamIdentification(long guildId, long channelId, long messageId, long authorId, String contentHash) { - private static ScamIdentification ofDatabaseRecord( - @NotNull ScamHistoryRecord scamHistoryRecord) { + @Nonnull + private static ScamIdentification ofDatabaseRecord(ScamHistoryRecord scamHistoryRecord) { return new ScamIdentification(scamHistoryRecord.getGuildId(), scamHistoryRecord.getChannelId(), scamHistoryRecord.getMessageId(), scamHistoryRecord.getAuthorId(), scamHistoryRecord.getContentHash()); diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/scam/package-info.java b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/scam/package-info.java index 40b4605eff..2cf89b16e6 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/scam/package-info.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/scam/package-info.java @@ -2,4 +2,7 @@ * This package offers classes dealing with detecting scam messages and taking appropriate action, * see {@link org.togetherjava.tjbot.commands.moderation.scam.ScamBlocker} as main entry point. */ +@ParametersAreNonnullByDefault package org.togetherjava.tjbot.commands.moderation.scam; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/temp/RevocableModerationAction.java b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/temp/RevocableModerationAction.java index b2cedc3007..b74290afd1 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/temp/RevocableModerationAction.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/temp/RevocableModerationAction.java @@ -3,9 +3,10 @@ import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.User; import net.dv8tion.jda.api.requests.RestAction; -import org.jetbrains.annotations.NotNull; import org.togetherjava.tjbot.commands.moderation.ModerationAction; +import javax.annotation.Nonnull; + /** * Represents revocable moderation actions, such as temporary bans. Primarily used by * {@link TemporaryModerationRoutine} to identify and revoke such actions. @@ -34,7 +35,7 @@ enum FailureIdentification { * * @return the type to apply the temporary action */ - @NotNull + @Nonnull ModerationAction getApplyType(); /** @@ -43,7 +44,7 @@ enum FailureIdentification { * * @return the type to revoke the temporary action */ - @NotNull + @Nonnull ModerationAction getRevokeType(); /** @@ -54,9 +55,8 @@ enum FailureIdentification { * @param reason why the action is revoked * @return the unsubmitted revocation action */ - @NotNull - RestAction revokeAction(@NotNull Guild guild, @NotNull User target, - @NotNull String reason); + @Nonnull + RestAction revokeAction(Guild guild, User target, String reason); /** * Handle a failure that might occur during revocation, i.e. execution of the action returned by @@ -67,6 +67,6 @@ RestAction revokeAction(@NotNull Guild guild, @NotNull User target, * @return a classification of the failure, decides whether the surrounding flow will continue * to handle the error further or not */ - @NotNull - FailureIdentification handleRevokeFailure(@NotNull Throwable failure, long targetId); + @Nonnull + FailureIdentification handleRevokeFailure(Throwable failure, long targetId); } diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/temp/RevocableRoleBasedAction.java b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/temp/RevocableRoleBasedAction.java index d289b00f49..bb7094f3a2 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/temp/RevocableRoleBasedAction.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/temp/RevocableRoleBasedAction.java @@ -1,10 +1,11 @@ package org.togetherjava.tjbot.commands.moderation.temp; import net.dv8tion.jda.api.exceptions.ErrorResponseException; -import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.annotation.Nonnull; + /** * Role based moderation actions that can be revoked, for example a {@link TemporaryMuteAction} or a * {@link TemporaryBanAction}, which are applied implicitly purely by the presence of a role. @@ -20,13 +21,13 @@ abstract class RevocableRoleBasedAction implements RevocableModerationAction { * @param actionName the action name to be used in logging in case of a failure, e.g. * {@code "mute"}, {@code "quarantine"} */ - RevocableRoleBasedAction(@NotNull String actionName) { + RevocableRoleBasedAction(String actionName) { this.actionName = actionName; } @Override - public @NotNull FailureIdentification handleRevokeFailure(@NotNull Throwable failure, - long targetId) { + @Nonnull + public FailureIdentification handleRevokeFailure(Throwable failure, long targetId) { if (failure instanceof ErrorResponseException errorResponseException) { switch (errorResponseException.getErrorResponse()) { diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/temp/TemporaryBanAction.java b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/temp/TemporaryBanAction.java index 0941224872..eaa42e34c9 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/temp/TemporaryBanAction.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/temp/TemporaryBanAction.java @@ -5,11 +5,12 @@ import net.dv8tion.jda.api.exceptions.ErrorResponseException; import net.dv8tion.jda.api.requests.ErrorResponse; import net.dv8tion.jda.api.requests.RestAction; -import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.togetherjava.tjbot.commands.moderation.ModerationAction; +import javax.annotation.Nonnull; + /** * Action to revoke temporary bans, as applied by * {@link org.togetherjava.tjbot.commands.moderation.BanCommand} and executed by @@ -19,24 +20,26 @@ final class TemporaryBanAction implements RevocableModerationAction { private static final Logger logger = LoggerFactory.getLogger(TemporaryBanAction.class); @Override - public @NotNull ModerationAction getApplyType() { + @Nonnull + public ModerationAction getApplyType() { return ModerationAction.BAN; } @Override - public @NotNull ModerationAction getRevokeType() { + @Nonnull + public ModerationAction getRevokeType() { return ModerationAction.UNBAN; } @Override - public @NotNull RestAction revokeAction(@NotNull Guild guild, @NotNull User target, - @NotNull String reason) { + @Nonnull + public RestAction revokeAction(Guild guild, User target, String reason) { return guild.unban(target).reason(reason); } @Override - public @NotNull FailureIdentification handleRevokeFailure(@NotNull Throwable failure, - long targetId) { + @Nonnull + public FailureIdentification handleRevokeFailure(Throwable failure, long targetId) { if (failure instanceof ErrorResponseException errorResponseException) { if (errorResponseException.getErrorResponse() == ErrorResponse.UNKNOWN_USER) { logger.debug( diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/temp/TemporaryModerationRoutine.java b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/temp/TemporaryModerationRoutine.java index 78e393e3b0..34df11d906 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/temp/TemporaryModerationRoutine.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/temp/TemporaryModerationRoutine.java @@ -4,7 +4,6 @@ import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.User; import net.dv8tion.jda.api.requests.RestAction; -import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.togetherjava.tjbot.commands.Routine; @@ -13,6 +12,7 @@ import org.togetherjava.tjbot.commands.moderation.ModerationActionsStore; import org.togetherjava.tjbot.config.Config; +import javax.annotation.Nonnull; import java.util.Map; import java.util.Objects; import java.util.Optional; @@ -43,8 +43,7 @@ public final class TemporaryModerationRoutine implements Routine { * @param actionsStore the store used to retrieve temporary moderation actions * @param config the config to use for this */ - public TemporaryModerationRoutine(@NotNull JDA jda, - @NotNull ModerationActionsStore actionsStore, @NotNull Config config) { + public TemporaryModerationRoutine(JDA jda, ModerationActionsStore actionsStore, Config config) { this.actionsStore = actionsStore; this.jda = jda; @@ -56,12 +55,13 @@ public TemporaryModerationRoutine(@NotNull JDA jda, } @Override - public void runRoutine(@NotNull JDA jda) { + public void runRoutine(JDA jda) { checkExpiredActions(); } @Override - public @NotNull Schedule createSchedule() { + @Nonnull + public Schedule createSchedule() { return new Schedule(ScheduleMode.FIXED_DELAY, 5, 5, TimeUnit.MINUTES); } @@ -78,7 +78,7 @@ private void checkExpiredActions() { logger.debug("Finished checking expired temporary moderation actions to revoke."); } - private void processGroupedActions(@NotNull RevocationGroupIdentifier groupIdentifier) { + private void processGroupedActions(RevocationGroupIdentifier groupIdentifier) { // Do not revoke an action which was overwritten by a still effective action that was issued // afterwards // For example if a user was perm-banned after being temp-banned @@ -108,7 +108,7 @@ private void processGroupedActions(@NotNull RevocationGroupIdentifier groupIdent revokeAction(groupIdentifier); } - private void revokeAction(@NotNull RevocationGroupIdentifier groupIdentifier) { + private void revokeAction(RevocationGroupIdentifier groupIdentifier) { Guild guild = jda.getGuildById(groupIdentifier.guildId); if (guild == null) { logger.debug( @@ -123,8 +123,9 @@ private void revokeAction(@NotNull RevocationGroupIdentifier groupIdentifier) { }, failure -> handleFailure(failure, groupIdentifier)); } - private @NotNull RestAction executeRevocation(@NotNull Guild guild, @NotNull User target, - @NotNull ModerationAction actionType) { + @Nonnull + private RestAction executeRevocation(Guild guild, User target, + ModerationAction actionType) { logger.info("Revoked temporary action {} against user '{}' ({}).", actionType, target.getAsTag(), target.getId()); RevocableModerationAction action = getRevocableActionByType(actionType); @@ -136,8 +137,7 @@ private void revokeAction(@NotNull RevocationGroupIdentifier groupIdentifier) { return action.revokeAction(guild, target, reason); } - private void handleFailure(@NotNull Throwable failure, - @NotNull RevocationGroupIdentifier groupIdentifier) { + private void handleFailure(Throwable failure, RevocationGroupIdentifier groupIdentifier) { if (getRevocableActionByType(groupIdentifier.type).handleRevokeFailure(failure, groupIdentifier.targetId) == RevocableModerationAction.FailureIdentification.KNOWN) { return; @@ -148,15 +148,14 @@ private void handleFailure(@NotNull Throwable failure, groupIdentifier.targetId, failure); } - private @NotNull RevocableModerationAction getRevocableActionByType( - @NotNull ModerationAction type) { + @Nonnull + private RevocableModerationAction getRevocableActionByType(ModerationAction type) { return Objects.requireNonNull(typeToRevocableAction.get(type), "Action type is not revocable: " + type); } - private record RevocationGroupIdentifier(long guildId, long targetId, - @NotNull ModerationAction type) { - static RevocationGroupIdentifier of(@NotNull ActionRecord actionRecord) { + private record RevocationGroupIdentifier(long guildId, long targetId, ModerationAction type) { + static RevocationGroupIdentifier of(ActionRecord actionRecord) { return new RevocationGroupIdentifier(actionRecord.guildId(), actionRecord.targetId(), actionRecord.actionType()); } diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/temp/TemporaryMuteAction.java b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/temp/TemporaryMuteAction.java index 921fe8b06a..94afeb2948 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/temp/TemporaryMuteAction.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/temp/TemporaryMuteAction.java @@ -3,11 +3,12 @@ import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.User; import net.dv8tion.jda.api.requests.RestAction; -import org.jetbrains.annotations.NotNull; import org.togetherjava.tjbot.commands.moderation.ModerationAction; import org.togetherjava.tjbot.commands.moderation.ModerationUtils; import org.togetherjava.tjbot.config.Config; +import javax.annotation.Nonnull; + /** * Action to revoke temporary mutes, as applied by * {@link org.togetherjava.tjbot.commands.moderation.MuteCommand} and executed by @@ -21,25 +22,27 @@ final class TemporaryMuteAction extends RevocableRoleBasedAction { * * @param config the config to use to identify the muted role */ - TemporaryMuteAction(@NotNull Config config) { + TemporaryMuteAction(Config config) { super("mute"); this.config = config; } @Override - public @NotNull ModerationAction getApplyType() { + @Nonnull + public ModerationAction getApplyType() { return ModerationAction.MUTE; } @Override - public @NotNull ModerationAction getRevokeType() { + @Nonnull + public ModerationAction getRevokeType() { return ModerationAction.UNMUTE; } @Override - public @NotNull RestAction revokeAction(@NotNull Guild guild, @NotNull User target, - @NotNull String reason) { + @Nonnull + public RestAction revokeAction(Guild guild, User target, String reason) { return guild .removeRoleFromMember(target.getIdLong(), ModerationUtils.getMutedRole(guild, config).orElseThrow()) diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/temp/TemporaryQuarantineAction.java b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/temp/TemporaryQuarantineAction.java index 2e8c7908a9..67393450f3 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/temp/TemporaryQuarantineAction.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/temp/TemporaryQuarantineAction.java @@ -3,11 +3,12 @@ import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.User; import net.dv8tion.jda.api.requests.RestAction; -import org.jetbrains.annotations.NotNull; import org.togetherjava.tjbot.commands.moderation.ModerationAction; import org.togetherjava.tjbot.commands.moderation.ModerationUtils; import org.togetherjava.tjbot.config.Config; +import javax.annotation.Nonnull; + /** * Action to revoke temporary quarantines, as applied by * {@link org.togetherjava.tjbot.commands.moderation.QuarantineCommand} and executed by @@ -21,25 +22,27 @@ final class TemporaryQuarantineAction extends RevocableRoleBasedAction { * * @param config the config to use to identify the quarantined role */ - TemporaryQuarantineAction(@NotNull Config config) { + TemporaryQuarantineAction(Config config) { super("quarantine"); this.config = config; } @Override - public @NotNull ModerationAction getApplyType() { + @Nonnull + public ModerationAction getApplyType() { return ModerationAction.QUARANTINE; } @Override - public @NotNull ModerationAction getRevokeType() { + @Nonnull + public ModerationAction getRevokeType() { return ModerationAction.UNQUARANTINE; } @Override - public @NotNull RestAction revokeAction(@NotNull Guild guild, @NotNull User target, - @NotNull String reason) { + @Nonnull + public RestAction revokeAction(Guild guild, User target, String reason) { return guild .removeRoleFromMember(target.getIdLong(), ModerationUtils.getQuarantinedRole(guild, config).orElseThrow()) diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/temp/package-info.java b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/temp/package-info.java index ba0fe1bbe9..6a11e771d2 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/temp/package-info.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/temp/package-info.java @@ -1,4 +1,7 @@ /** * This package offers classes dealing with temporary moderation actions, such as temporary bans. */ +@ParametersAreNonnullByDefault package org.togetherjava.tjbot.commands.moderation.temp; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/package-info.java b/application/src/main/java/org/togetherjava/tjbot/commands/package-info.java index 17e5e3366c..0a00d8bf03 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/package-info.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/package-info.java @@ -9,4 +9,7 @@ * {@link org.togetherjava.tjbot.commands.SlashCommand} or using the adapter * {@link org.togetherjava.tjbot.commands.SlashCommandAdapter} for convenience. */ +@ParametersAreNonnullByDefault package org.togetherjava.tjbot.commands; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/reminder/RemindCommand.java b/application/src/main/java/org/togetherjava/tjbot/commands/reminder/RemindCommand.java index ef1128f035..513e72438a 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/reminder/RemindCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/reminder/RemindCommand.java @@ -7,11 +7,11 @@ import net.dv8tion.jda.api.interactions.callbacks.IReplyCallback; import net.dv8tion.jda.api.interactions.commands.OptionType; import net.dv8tion.jda.api.interactions.commands.build.OptionData; -import org.jetbrains.annotations.NotNull; import org.togetherjava.tjbot.commands.SlashCommandAdapter; import org.togetherjava.tjbot.commands.SlashCommandVisibility; import org.togetherjava.tjbot.db.Database; +import javax.annotation.Nonnull; import java.time.*; import java.time.temporal.TemporalAmount; import java.util.List; @@ -52,7 +52,7 @@ public final class RemindCommand extends SlashCommandAdapter { * * @param database to store and fetch the reminders from */ - public RemindCommand(@NotNull Database database) { + public RemindCommand(Database database) { super(COMMAND_NAME, "Reminds you after a given time period has passed (e.g. in 5 weeks)", SlashCommandVisibility.GUILD); @@ -72,7 +72,7 @@ public RemindCommand(@NotNull Database database) { } @Override - public void onSlashCommand(@NotNull SlashCommandInteractionEvent event) { + public void onSlashCommand(SlashCommandInteractionEvent event) { int timeAmount = Math.toIntExact(event.getOption(TIME_AMOUNT_OPTION).getAsLong()); String timeUnit = event.getOption(TIME_UNIT_OPTION).getAsString(); String content = event.getOption(CONTENT_OPTION).getAsString(); @@ -102,7 +102,8 @@ public void onSlashCommand(@NotNull SlashCommandInteractionEvent event) { .insert()); } - private static @NotNull Instant parseWhen(int whenAmount, @NotNull String whenUnit) { + @Nonnull + private static Instant parseWhen(int whenAmount, String whenUnit) { TemporalAmount period = switch (whenUnit) { case "second", "seconds" -> Duration.ofSeconds(whenAmount); case "minute", "minutes" -> Duration.ofMinutes(whenAmount); @@ -117,8 +118,7 @@ public void onSlashCommand(@NotNull SlashCommandInteractionEvent event) { return ZonedDateTime.now(ZoneOffset.UTC).plus(period).toInstant(); } - private static boolean handleIsRemindAtWithinLimits(@NotNull Instant remindAt, - @NotNull IReplyCallback event) { + private static boolean handleIsRemindAtWithinLimits(Instant remindAt, IReplyCallback event) { ZonedDateTime maxWhen = ZonedDateTime.now(ZoneOffset.UTC).plus(MAX_TIME_PERIOD); if (remindAt.atZone(ZoneOffset.UTC).isBefore(maxWhen)) { @@ -134,8 +134,8 @@ private static boolean handleIsRemindAtWithinLimits(@NotNull Instant remindAt, return false; } - private boolean handleIsUserBelowMaxPendingReminders(@NotNull ISnowflake author, - @NotNull ISnowflake guild, @NotNull IReplyCallback event) { + private boolean handleIsUserBelowMaxPendingReminders(ISnowflake author, ISnowflake guild, + IReplyCallback event) { int pendingReminders = database.read(context -> context.fetchCount(PENDING_REMINDERS, PENDING_REMINDERS.AUTHOR_ID.equal(author.getIdLong()) .and(PENDING_REMINDERS.GUILD_ID.equal(guild.getIdLong())))); diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/reminder/RemindRoutine.java b/application/src/main/java/org/togetherjava/tjbot/commands/reminder/RemindRoutine.java index 563231afb3..c755fe0974 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/reminder/RemindRoutine.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/reminder/RemindRoutine.java @@ -2,17 +2,20 @@ import net.dv8tion.jda.api.EmbedBuilder; import net.dv8tion.jda.api.JDA; -import net.dv8tion.jda.api.entities.*; +import net.dv8tion.jda.api.entities.MessageChannel; +import net.dv8tion.jda.api.entities.MessageEmbed; +import net.dv8tion.jda.api.entities.PrivateChannel; +import net.dv8tion.jda.api.entities.User; import net.dv8tion.jda.api.requests.RestAction; import net.dv8tion.jda.api.requests.restaction.MessageAction; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.togetherjava.tjbot.commands.Routine; import org.togetherjava.tjbot.db.Database; -import java.awt.*; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.awt.Color; import java.time.Instant; import java.time.temporal.TemporalAccessor; import java.util.concurrent.TimeUnit; @@ -37,18 +40,19 @@ public final class RemindRoutine implements Routine { * * @param database the database that contains the pending reminders to send. */ - public RemindRoutine(@NotNull Database database) { + public RemindRoutine(Database database) { this.database = database; } @Override - public @NotNull Schedule createSchedule() { + @Nonnull + public Schedule createSchedule() { return new Schedule(ScheduleMode.FIXED_RATE, 0, SCHEDULE_INTERVAL_SECONDS, TimeUnit.SECONDS); } @Override - public void runRoutine(@NotNull JDA jda) { + public void runRoutine(JDA jda) { Instant now = Instant.now(); database.write(context -> context.selectFrom(PENDING_REMINDERS) .where(PENDING_REMINDERS.REMIND_AT.lessOrEqual(now)) @@ -62,13 +66,13 @@ public void runRoutine(@NotNull JDA jda) { })); } - private static void sendReminder(@NotNull JDA jda, long id, long channelId, long authorId, - @NotNull CharSequence content, @NotNull TemporalAccessor createdAt) { + private static void sendReminder(JDA jda, long id, long channelId, long authorId, + CharSequence content, TemporalAccessor createdAt) { RestAction route = computeReminderRoute(jda, channelId, authorId); sendReminderViaRoute(route, id, content, createdAt); } - private static RestAction computeReminderRoute(@NotNull JDA jda, long channelId, + private static RestAction computeReminderRoute(JDA jda, long channelId, long authorId) { // If guild channel can still be found, send there MessageChannel channel = jda.getChannelById(MessageChannel.class, channelId); @@ -80,20 +84,21 @@ private static RestAction computeReminderRoute(@NotNull JDA jda, return createDmReminderRoute(jda, authorId); } - private static @NotNull RestAction createGuildReminderRoute(@NotNull JDA jda, - long authorId, @NotNull MessageChannel channel) { + @Nonnull + private static RestAction createGuildReminderRoute(JDA jda, long authorId, + MessageChannel channel) { return jda.retrieveUserById(authorId) .onErrorMap(error -> null) .map(author -> ReminderRoute.toPublic(channel, author)); } - private static @NotNull RestAction createDmReminderRoute(@NotNull JDA jda, - long authorId) { + @Nonnull + private static RestAction createDmReminderRoute(JDA jda, long authorId) { return jda.openPrivateChannelById(authorId).map(ReminderRoute::toPrivate); } - private static void sendReminderViaRoute(@NotNull RestAction routeAction, - long id, @NotNull CharSequence content, @NotNull TemporalAccessor createdAt) { + private static void sendReminderViaRoute(RestAction routeAction, long id, + CharSequence content, TemporalAccessor createdAt) { Function sendMessage = route -> route.channel .sendMessageEmbeds(createReminderEmbed(content, createdAt, route.target())) .content(route.description()); @@ -108,8 +113,9 @@ Failed to send a reminder (id '{}'), skipping it. This can be due to a network i routeAction.flatMap(sendMessage).queue(doNothing(), logFailure); } - private static @NotNull MessageEmbed createReminderEmbed(@NotNull CharSequence content, - @NotNull TemporalAccessor createdAt, @Nullable User author) { + @Nonnull + private static MessageEmbed createReminderEmbed(CharSequence content, + TemporalAccessor createdAt, @Nullable User author) { String authorName = author == null ? "Unknown user" : author.getAsTag(); String authorIconUrl = author == null ? null : author.getAvatarUrl(); @@ -121,19 +127,22 @@ Failed to send a reminder (id '{}'), skipping it. This can be due to a network i .build(); } - private static @NotNull Consumer doNothing() { + @Nonnull + private static Consumer doNothing() { return a -> { }; } - private record ReminderRoute(@NotNull MessageChannel channel, @Nullable User target, + private record ReminderRoute(MessageChannel channel, @Nullable User target, @Nullable String description) { - static ReminderRoute toPublic(@NotNull MessageChannel channel, @Nullable User target) { + @Nonnull + static ReminderRoute toPublic(MessageChannel channel, @Nullable User target) { return new ReminderRoute(channel, target, target == null ? null : target.getAsMention()); } - static ReminderRoute toPrivate(@NotNull PrivateChannel channel) { + @Nonnull + static ReminderRoute toPrivate(PrivateChannel channel) { return new ReminderRoute(channel, channel.getUser(), "(Sending your reminder directly, because I was unable to locate" + " the original channel you wanted it to be send to)"); diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/reminder/package-info.java b/application/src/main/java/org/togetherjava/tjbot/commands/reminder/package-info.java index a7f65b1c69..f4fab40923 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/reminder/package-info.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/reminder/package-info.java @@ -2,4 +2,7 @@ * This packages offers all the functionality for the remind-command. The core class is * {@link org.togetherjava.tjbot.commands.reminder.RemindCommand}. */ +@ParametersAreNonnullByDefault package org.togetherjava.tjbot.commands.reminder; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/system/BotCore.java b/application/src/main/java/org/togetherjava/tjbot/commands/system/BotCore.java index 5921a542fc..f7eae74609 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/system/BotCore.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/system/BotCore.java @@ -15,7 +15,6 @@ import net.dv8tion.jda.api.interactions.commands.Command; import net.dv8tion.jda.api.interactions.components.ComponentInteraction; import net.dv8tion.jda.api.requests.ErrorResponse; -import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.togetherjava.tjbot.commands.*; @@ -26,6 +25,7 @@ import org.togetherjava.tjbot.config.Config; import org.togetherjava.tjbot.db.Database; +import javax.annotation.Nonnull; import java.util.*; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -71,8 +71,7 @@ public final class BotCore extends ListenerAdapter implements SlashCommandProvid * @param database the database that commands may use to persist data * @param config the configuration to use for this system */ - @SuppressWarnings("ThisEscapedInObjectConstruction") - public BotCore(@NotNull JDA jda, @NotNull Database database, @NotNull Config config) { + public BotCore(JDA jda, Database database, Config config) { this.config = config; Collection features = Features.createFeatures(jda, database, config); @@ -126,7 +125,8 @@ public BotCore(@NotNull JDA jda, @NotNull Database database, @NotNull Config con } @Override - public @NotNull Collection getSlashCommands() { + @Nonnull + public Collection getSlashCommands() { return nameToInteractor.values() .stream() .filter(SlashCommand.class::isInstance) @@ -135,7 +135,8 @@ public BotCore(@NotNull JDA jda, @NotNull Database database, @NotNull Config con } @Override - public @NotNull Optional getSlashCommand(@NotNull String name) { + @Nonnull + public Optional getSlashCommand(String name) { return Optional.ofNullable(nameToInteractor.get(name)) .filter(SlashCommand.class::isInstance) .map(SlashCommand.class::cast); @@ -146,7 +147,7 @@ public BotCore(@NotNull JDA jda, @NotNull Database database, @NotNull Config con * * @param jda the JDA instance to work with */ - public void onReady(@NotNull JDA jda) { + public void onReady(JDA jda) { if (!receivedOnReady.compareAndSet(false, true)) { // Ensures that we only enter the event once return; @@ -163,7 +164,7 @@ public void onReady(@NotNull JDA jda) { scheduleRoutines(jda); } - private void scheduleRoutines(@NotNull JDA jda) { + private void scheduleRoutines(JDA jda) { routines.forEach(routine -> { Runnable command = () -> { String routineName = routine.getClass().getSimpleName(); @@ -188,7 +189,7 @@ private void scheduleRoutines(@NotNull JDA jda) { } @Override - public void onMessageReceived(@NotNull final MessageReceivedEvent event) { + public void onMessageReceived(final MessageReceivedEvent event) { if (event.isFromGuild()) { getMessageReceiversSubscribedTo(event.getChannel()) .forEach(messageReceiver -> messageReceiver.onMessageReceived(event)); @@ -196,15 +197,15 @@ public void onMessageReceived(@NotNull final MessageReceivedEvent event) { } @Override - public void onMessageUpdate(@NotNull final MessageUpdateEvent event) { + public void onMessageUpdate(final MessageUpdateEvent event) { if (event.isFromGuild()) { getMessageReceiversSubscribedTo(event.getChannel()) .forEach(messageReceiver -> messageReceiver.onMessageUpdated(event)); } } - private @NotNull Stream getMessageReceiversSubscribedTo( - @NotNull Channel channel) { + @Nonnull + private Stream getMessageReceiversSubscribedTo(Channel channel) { String channelName = channel.getName(); return channelNameToMessageReceiver.entrySet() .stream() @@ -215,14 +216,14 @@ public void onMessageUpdate(@NotNull final MessageUpdateEvent event) { } @Override - public void onSlashCommandInteraction(@NotNull SlashCommandInteractionEvent event) { + public void onSlashCommandInteraction(SlashCommandInteractionEvent event) { logger.debug("Received slash command '{}' (#{}) on guild '{}'", event.getName(), event.getId(), event.getGuild()); COMMAND_SERVICE.execute(() -> requireSlashCommand(event.getName()).onSlashCommand(event)); } @Override - public void onButtonInteraction(@NotNull ButtonInteractionEvent event) { + public void onButtonInteraction(ButtonInteractionEvent event) { logger.debug("Received button click '{}' (#{}) on guild '{}'", event.getComponentId(), event.getId(), event.getGuild()); COMMAND_SERVICE @@ -230,14 +231,14 @@ public void onButtonInteraction(@NotNull ButtonInteractionEvent event) { } @Override - public void onSelectMenuInteraction(@NotNull SelectMenuInteractionEvent event) { + public void onSelectMenuInteraction(SelectMenuInteractionEvent event) { logger.debug("Received selection menu event '{}' (#{}) on guild '{}'", event.getComponentId(), event.getId(), event.getGuild()); COMMAND_SERVICE .execute(() -> forwardComponentCommand(event, UserInteractor::onSelectionMenu)); } - private void registerReloadCommand(@NotNull Guild guild) { + private void registerReloadCommand(Guild guild) { guild.retrieveCommands().queue(commands -> { // Has it been registered already? if (commands.stream().map(Command::getName).anyMatch(RELOAD_COMMAND::equals)) { @@ -257,7 +258,6 @@ private void registerReloadCommand(@NotNull Guild guild) { /** * Forwards the given component event to the associated user interactor. *

    - *

    * An example call might look like: * *

    @@ -271,8 +271,8 @@ private void registerReloadCommand(@NotNull Guild guild) {
          *        providing the event and list of arguments for consumption
          * @param  the type of the component interaction that should be forwarded
          */
    -    private  void forwardComponentCommand(@NotNull T event,
    -            @NotNull TriConsumer> interactorArgumentConsumer) {
    +    private  void forwardComponentCommand(T event,
    +            TriConsumer> interactorArgumentConsumer) {
             Optional componentIdOpt;
             try {
                 componentIdOpt = componentIdParser.parse(event.getComponentId());
    @@ -308,7 +308,8 @@ private  void forwardComponentCommand(@NotNull T
          * @return the command with the given name
          * @throws NullPointerException if the command with the given name was not registered
          */
    -    private @NotNull SlashCommand requireSlashCommand(@NotNull String name) {
    +    @Nonnull
    +    private SlashCommand requireSlashCommand(String name) {
             return getSlashCommand(name).orElseThrow(
                     () -> new NullPointerException("There is no slash command with name " + name));
         }
    @@ -320,7 +321,8 @@ private  void forwardComponentCommand(@NotNull T
          * @return the user interactor with the given name
          * @throws NullPointerException if the user interactor with the given name was not registered
          */
    -    private @NotNull UserInteractor requireUserInteractor(@NotNull String name) {
    +    @Nonnull
    +    private UserInteractor requireUserInteractor(String name) {
             return Objects.requireNonNull(nameToInteractor.get(name));
         }
     
    diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/system/LogLevelCommand.java b/application/src/main/java/org/togetherjava/tjbot/commands/system/LogLevelCommand.java
    index 32a66f41b4..2780f3f8ed 100644
    --- a/application/src/main/java/org/togetherjava/tjbot/commands/system/LogLevelCommand.java
    +++ b/application/src/main/java/org/togetherjava/tjbot/commands/system/LogLevelCommand.java
    @@ -6,7 +6,6 @@
     import org.apache.logging.log4j.Level;
     import org.apache.logging.log4j.LogManager;
     import org.apache.logging.log4j.core.config.Configurator;
    -import org.jetbrains.annotations.NotNull;
     import org.togetherjava.tjbot.commands.SlashCommandAdapter;
     import org.togetherjava.tjbot.commands.SlashCommandVisibility;
     
    @@ -44,7 +43,7 @@ public LogLevelCommand() {
         // Security warning about changing log configs. We only change the level, that is safe.
         @SuppressWarnings("squid:S4792")
         @Override
    -    public void onSlashCommand(@NotNull SlashCommandInteractionEvent event) {
    +    public void onSlashCommand(SlashCommandInteractionEvent event) {
             String levelText = event.getOption(LOG_LEVEL_OPTION).getAsString();
             Level level = Level.getLevel(levelText);
     
    diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/system/ReloadCommand.java b/application/src/main/java/org/togetherjava/tjbot/commands/system/ReloadCommand.java
    index 256faa7d5e..1b715aad28 100644
    --- a/application/src/main/java/org/togetherjava/tjbot/commands/system/ReloadCommand.java
    +++ b/application/src/main/java/org/togetherjava/tjbot/commands/system/ReloadCommand.java
    @@ -10,7 +10,6 @@
     import net.dv8tion.jda.api.interactions.components.buttons.ButtonStyle;
     import net.dv8tion.jda.api.requests.RestAction;
     import net.dv8tion.jda.api.requests.restaction.CommandListUpdateAction;
    -import org.jetbrains.annotations.NotNull;
     import org.slf4j.Logger;
     import org.slf4j.LoggerFactory;
     import org.togetherjava.tjbot.commands.SlashCommand;
    @@ -18,6 +17,7 @@
     import org.togetherjava.tjbot.commands.SlashCommandVisibility;
     import org.togetherjava.tjbot.commands.utils.MessageUtils;
     
    +import javax.annotation.Nonnull;
     import java.util.ArrayList;
     import java.util.Collections;
     import java.util.List;
    @@ -46,7 +46,7 @@ public final class ReloadCommand extends SlashCommandAdapter {
          * @param commandProvider the provider of slash commands to reload when this command is
          *        triggered
          */
    -    public ReloadCommand(@NotNull SlashCommandProvider commandProvider) {
    +    public ReloadCommand(SlashCommandProvider commandProvider) {
             super("reload",
                     "Uploads all existing slash-commands to Discord so they are fully up-to-date.",
                     SlashCommandVisibility.GUILD);
    @@ -54,7 +54,7 @@ public ReloadCommand(@NotNull SlashCommandProvider commandProvider) {
         }
     
         @Override
    -    public void onSlashCommand(@NotNull SlashCommandInteractionEvent event) {
    +    public void onSlashCommand(SlashCommandInteractionEvent event) {
             Member member = Objects.requireNonNull(event.getMember());
     
             if (!member.hasPermission(Permission.MANAGE_SERVER)) {
    @@ -75,7 +75,7 @@ public void onSlashCommand(@NotNull SlashCommandInteractionEvent event) {
         }
     
         @Override
    -    public void onButtonClick(@NotNull ButtonInteractionEvent event, @NotNull List args) {
    +    public void onButtonClick(ButtonInteractionEvent event, List args) {
             // Ignore if another user clicked the button
             String userId = args.get(0);
             if (!userId.equals(Objects.requireNonNull(event.getMember()).getId())) {
    @@ -102,8 +102,7 @@ public void onButtonClick(@NotNull ButtonInteractionEvent event, @NotNull List updateCommandsIf(
    @@ -133,9 +132,9 @@ public void onButtonClick(@NotNull ButtonInteractionEvent event, @NotNull List commandFilter,
    -            @NotNull CommandListUpdateAction updateAction) {
    +    @Nonnull
    +    private CommandListUpdateAction updateCommandsIf(Predicate commandFilter,
    +            CommandListUpdateAction updateAction) {
             return commandProvider.getSlashCommands()
                 .stream()
                 .filter(commandFilter)
    @@ -143,12 +142,13 @@ public void onButtonClick(@NotNull ButtonInteractionEvent event, @NotNull List x);
         }
     
    -    private static @NotNull CommandListUpdateAction getGlobalUpdateAction(@NotNull JDA jda) {
    +    @Nonnull
    +    private static CommandListUpdateAction getGlobalUpdateAction(JDA jda) {
             return jda.updateCommands();
         }
     
    -    private static @NotNull Stream getGuildUpdateActions(
    -            @NotNull JDA jda) {
    +    @Nonnull
    +    private static Stream getGuildUpdateActions(JDA jda) {
             return jda.getGuildCache().stream().map(Guild::updateCommands);
         }
     }
    diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/system/SlashCommandProvider.java b/application/src/main/java/org/togetherjava/tjbot/commands/system/SlashCommandProvider.java
    index ef9f0357bd..85cc662511 100644
    --- a/application/src/main/java/org/togetherjava/tjbot/commands/system/SlashCommandProvider.java
    +++ b/application/src/main/java/org/togetherjava/tjbot/commands/system/SlashCommandProvider.java
    @@ -1,8 +1,8 @@
     package org.togetherjava.tjbot.commands.system;
     
    -import org.jetbrains.annotations.NotNull;
     import org.togetherjava.tjbot.commands.SlashCommand;
     
    +import javax.annotation.Nonnull;
     import java.util.Collection;
     import java.util.Optional;
     
    @@ -15,7 +15,7 @@ public interface SlashCommandProvider {
          *
          * @return all slash commands
          */
    -    @NotNull
    +    @Nonnull
         Collection getSlashCommands();
     
         /**
    @@ -24,6 +24,6 @@ public interface SlashCommandProvider {
          * @param name the name of the command
          * @return the command registered under this name, if any
          */
    -    @NotNull
    -    Optional getSlashCommand(@NotNull String name);
    +    @Nonnull
    +    Optional getSlashCommand(String name);
     }
    diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/system/package-info.java b/application/src/main/java/org/togetherjava/tjbot/commands/system/package-info.java
    index 60670e1341..48e796b587 100644
    --- a/application/src/main/java/org/togetherjava/tjbot/commands/system/package-info.java
    +++ b/application/src/main/java/org/togetherjava/tjbot/commands/system/package-info.java
    @@ -2,4 +2,7 @@
      * This package represents the core of the command system. The entry point is
      * {@link org.togetherjava.tjbot.commands.system.BotCore}.
      */
    +@ParametersAreNonnullByDefault
     package org.togetherjava.tjbot.commands.system;
    +
    +import javax.annotation.ParametersAreNonnullByDefault;
    diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/tags/TagCommand.java b/application/src/main/java/org/togetherjava/tjbot/commands/tags/TagCommand.java
    index de59fae319..615ee799ae 100644
    --- a/application/src/main/java/org/togetherjava/tjbot/commands/tags/TagCommand.java
    +++ b/application/src/main/java/org/togetherjava/tjbot/commands/tags/TagCommand.java
    @@ -5,7 +5,6 @@
     import net.dv8tion.jda.api.interactions.commands.OptionMapping;
     import net.dv8tion.jda.api.interactions.commands.OptionType;
     import net.dv8tion.jda.api.requests.restaction.interactions.ReplyCallbackAction;
    -import org.jetbrains.annotations.NotNull;
     import org.togetherjava.tjbot.commands.SlashCommandAdapter;
     import org.togetherjava.tjbot.commands.SlashCommandVisibility;
     
    @@ -43,7 +42,7 @@ public TagCommand(TagSystem tagSystem) {
         }
     
         @Override
    -    public void onSlashCommand(@NotNull SlashCommandInteractionEvent event) {
    +    public void onSlashCommand(SlashCommandInteractionEvent event) {
             String id = Objects.requireNonNull(event.getOption(ID_OPTION)).getAsString();
             OptionMapping replyToUserOption = event.getOption(REPLY_TO_USER_OPTION);
     
    diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/tags/TagManageCommand.java b/application/src/main/java/org/togetherjava/tjbot/commands/tags/TagManageCommand.java
    index 3cebad8f10..9b34c08a12 100644
    --- a/application/src/main/java/org/togetherjava/tjbot/commands/tags/TagManageCommand.java
    +++ b/application/src/main/java/org/togetherjava/tjbot/commands/tags/TagManageCommand.java
    @@ -10,21 +10,18 @@
     import net.dv8tion.jda.api.interactions.commands.OptionType;
     import net.dv8tion.jda.api.interactions.commands.build.SubcommandData;
     import net.dv8tion.jda.api.requests.ErrorResponse;
    -import org.jetbrains.annotations.NotNull;
    -import org.jetbrains.annotations.Nullable;
     import org.slf4j.Logger;
     import org.slf4j.LoggerFactory;
     import org.togetherjava.tjbot.commands.SlashCommandAdapter;
     import org.togetherjava.tjbot.commands.SlashCommandVisibility;
     import org.togetherjava.tjbot.moderation.ModAuditLogWriter;
     
    -import java.time.temporal.TemporalAccessor;
    -import java.util.*;
    -import java.util.NoSuchElementException;
    +import javax.annotation.Nonnull;
    +import javax.annotation.Nullable;
     import java.nio.charset.StandardCharsets;
     import java.time.Instant;
    -import java.util.Objects;
    -import java.util.OptionalLong;
    +import java.time.temporal.TemporalAccessor;
    +import java.util.*;
     import java.util.function.BiConsumer;
     import java.util.function.Consumer;
     
    @@ -71,8 +68,7 @@ public final class TagManageCommand extends SlashCommandAdapter {
          * @param tagSystem the system providing the actual tag data
          * @param modAuditLogWriter to log tag changes for audition
          */
    -    public TagManageCommand(@NotNull TagSystem tagSystem,
    -            @NotNull ModAuditLogWriter modAuditLogWriter) {
    +    public TagManageCommand(TagSystem tagSystem, ModAuditLogWriter modAuditLogWriter) {
             super("tag-manage", "Provides commands to manage all tags", SlashCommandVisibility.GUILD);
     
             this.tagSystem = tagSystem;
    @@ -104,8 +100,7 @@ public TagManageCommand(@NotNull TagSystem tagSystem,
                         .addOption(OptionType.STRING, ID_OPTION, ID_DESCRIPTION, true));
         }
     
    -    private static void sendSuccessMessage(@NotNull IReplyCallback event, @NotNull String id,
    -            @NotNull String actionVerb) {
    +    private static void sendSuccessMessage(IReplyCallback event, String id, String actionVerb) {
             logger.info("User '{}' {} the tag with id '{}'.", event.getUser().getId(), actionVerb, id);
     
             event
    @@ -128,8 +123,8 @@ private static void sendSuccessMessage(@NotNull IReplyCallback event, @NotNull S
          * @param event the event to send messages with
          * @return the parsed message id, if successful
          */
    -    private static OptionalLong parseMessageIdAndHandle(@NotNull String messageId,
    -            @NotNull IReplyCallback event) {
    +    @Nonnull
    +    private static OptionalLong parseMessageIdAndHandle(String messageId, IReplyCallback event) {
             try {
                 return OptionalLong.of(Long.parseLong(messageId));
             } catch (NumberFormatException e) {
    @@ -143,7 +138,7 @@ private static OptionalLong parseMessageIdAndHandle(@NotNull String messageId,
         }
     
         @Override
    -    public void onSlashCommand(@NotNull SlashCommandInteractionEvent event) {
    +    public void onSlashCommand(SlashCommandInteractionEvent event) {
             switch (Subcommand.fromName(event.getSubcommandName())) {
                 case RAW -> rawTag(event);
                 case CREATE -> createTag(event);
    @@ -156,7 +151,7 @@ public void onSlashCommand(@NotNull SlashCommandInteractionEvent event) {
             }
         }
     
    -    private void rawTag(@NotNull SlashCommandInteractionEvent event) {
    +    private void rawTag(SlashCommandInteractionEvent event) {
             String id = Objects.requireNonNull(event.getOption(ID_OPTION)).getAsString();
             if (tagSystem.handleIsUnknownTag(id, event)) {
                 return;
    @@ -168,31 +163,31 @@ private void rawTag(@NotNull SlashCommandInteractionEvent event) {
                 .queue();
         }
     
    -    private void createTag(@NotNull CommandInteraction event) {
    +    private void createTag(CommandInteraction event) {
             String content = Objects.requireNonNull(event.getOption(CONTENT_OPTION)).getAsString();
     
             handleAction(TagStatus.NOT_EXISTS, id -> tagSystem.putTag(id, content), event,
                     Subcommand.CREATE, content);
         }
     
    -    private void createTagWithMessage(@NotNull CommandInteraction event) {
    +    private void createTagWithMessage(CommandInteraction event) {
             handleActionWithMessage(TagStatus.NOT_EXISTS, tagSystem::putTag, event,
                     Subcommand.CREATE_WITH_MESSAGE);
         }
     
    -    private void editTag(@NotNull CommandInteraction event) {
    +    private void editTag(CommandInteraction event) {
             String content = Objects.requireNonNull(event.getOption(CONTENT_OPTION)).getAsString();
     
             handleAction(TagStatus.EXISTS, id -> tagSystem.putTag(id, content), event, Subcommand.EDIT,
                     content);
         }
     
    -    private void editTagWithMessage(@NotNull CommandInteraction event) {
    +    private void editTagWithMessage(CommandInteraction event) {
             handleActionWithMessage(TagStatus.EXISTS, tagSystem::putTag, event,
                     Subcommand.EDIT_WITH_MESSAGE);
         }
     
    -    private void deleteTag(@NotNull CommandInteraction event) {
    +    private void deleteTag(CommandInteraction event) {
             handleAction(TagStatus.EXISTS, tagSystem::deleteTag, event, Subcommand.DELETE, null);
         }
     
    @@ -208,9 +203,8 @@ private void deleteTag(@NotNull CommandInteraction event) {
          * @param subcommand the subcommand to be executed
          * @param newContent the new content of the tag, or null if content is unchanged
          */
    -    private void handleAction(@NotNull TagStatus requiredTagStatus,
    -            @NotNull Consumer idAction, @NotNull CommandInteraction event,
    -            @NotNull Subcommand subcommand, @Nullable String newContent) {
    +    private void handleAction(TagStatus requiredTagStatus, Consumer idAction,
    +            CommandInteraction event, Subcommand subcommand, @Nullable String newContent) {
     
             String id = Objects.requireNonNull(event.getOption(ID_OPTION)).getAsString();
             if (isWrongTagStatusAndHandle(requiredTagStatus, id, event)) {
    @@ -243,9 +237,9 @@ private void handleAction(@NotNull TagStatus requiredTagStatus,
          *        {@code message-id} option set
          * @param subcommand the subcommand to be executed
          */
    -    private void handleActionWithMessage(@NotNull TagStatus requiredTagStatus,
    -            @NotNull BiConsumer idAndContentAction,
    -            @NotNull CommandInteraction event, @NotNull Subcommand subcommand) {
    +    private void handleActionWithMessage(TagStatus requiredTagStatus,
    +            BiConsumer idAndContentAction, CommandInteraction event,
    +            Subcommand subcommand) {
     
             String tagId = Objects.requireNonNull(event.getOption(ID_OPTION)).getAsString();
             OptionalLong messageIdOpt = parseMessageIdAndHandle(
    @@ -295,8 +289,8 @@ private void handleActionWithMessage(@NotNull TagStatus requiredTagStatus,
          * @param id the id of the tag to get its content
          * @return the content of the tag, if present
          */
    -    private @NotNull Optional getTagContent(@NotNull Subcommand subcommand,
    -            @NotNull String id) {
    +    @Nonnull
    +    private Optional getTagContent(Subcommand subcommand, String id) {
             if (Subcommand.SUBCOMMANDS_WITH_PREVIOUS_CONTENT.contains(subcommand)) {
                 try {
                     return tagSystem.getTag(id);
    @@ -322,8 +316,8 @@ private void handleActionWithMessage(@NotNull TagStatus requiredTagStatus,
          * @param event the event to send messages with
          * @return whether the status of the given tag is not equal to the required status
          */
    -    private boolean isWrongTagStatusAndHandle(@NotNull TagStatus requiredTagStatus,
    -            @NotNull String id, @NotNull IReplyCallback event) {
    +    private boolean isWrongTagStatusAndHandle(TagStatus requiredTagStatus, String id,
    +            IReplyCallback event) {
             if (requiredTagStatus == TagStatus.EXISTS) {
                 return tagSystem.handleIsUnknownTag(id, event);
             } else if (requiredTagStatus == TagStatus.NOT_EXISTS) {
    @@ -339,9 +333,9 @@ private boolean isWrongTagStatusAndHandle(@NotNull TagStatus requiredTagStatus,
             return false;
         }
     
    -    private void logAction(@NotNull Subcommand subcommand, @NotNull Guild guild,
    -            @NotNull User author, @NotNull TemporalAccessor triggeredAt, @NotNull String id,
    -            @Nullable String newContent, @Nullable String previousContent) {
    +    private void logAction(Subcommand subcommand, Guild guild, User author,
    +            TemporalAccessor triggeredAt, String id, @Nullable String newContent,
    +            @Nullable String previousContent) {
     
             List attachments = new ArrayList<>();
     
    @@ -407,17 +401,18 @@ enum Subcommand {
             private final String name;
             private final String actionVerb;
     
    -        Subcommand(@NotNull String name, @NotNull String actionVerb) {
    +        Subcommand(String name, String actionVerb) {
                 this.name = name;
                 this.actionVerb = actionVerb;
             }
     
    -        @NotNull
    +        @Nonnull
             String getName() {
                 return name;
             }
     
    -        static Subcommand fromName(@NotNull String name) {
    +        @Nonnull
    +        static Subcommand fromName(String name) {
                 for (Subcommand subcommand : Subcommand.values()) {
                     if (subcommand.name.equals(name)) {
                         return subcommand;
    @@ -427,7 +422,7 @@ static Subcommand fromName(@NotNull String name) {
                         "Subcommand with name '%s' is unknown".formatted(name));
             }
     
    -        @NotNull
    +        @Nonnull
             String getActionVerb() {
                 return actionVerb;
             }
    diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/tags/TagSystem.java b/application/src/main/java/org/togetherjava/tjbot/commands/tags/TagSystem.java
    index 0a9090d0d5..13272b6131 100644
    --- a/application/src/main/java/org/togetherjava/tjbot/commands/tags/TagSystem.java
    +++ b/application/src/main/java/org/togetherjava/tjbot/commands/tags/TagSystem.java
    @@ -4,13 +4,13 @@
     import net.dv8tion.jda.api.interactions.callbacks.IReplyCallback;
     import net.dv8tion.jda.api.interactions.components.buttons.Button;
     import net.dv8tion.jda.api.interactions.components.buttons.ButtonStyle;
    -import org.jetbrains.annotations.NotNull;
     import org.togetherjava.tjbot.commands.utils.StringDistances;
     import org.togetherjava.tjbot.db.Database;
     import org.togetherjava.tjbot.db.generated.tables.Tags;
     import org.togetherjava.tjbot.db.generated.tables.records.TagsRecord;
     
    -import java.awt.*;
    +import javax.annotation.Nonnull;
    +import java.awt.Color;
     import java.util.Optional;
     import java.util.Set;
     import java.util.stream.Collectors;
    @@ -58,8 +58,7 @@ static Button createDeleteButton(String componentId) {
          * @param event the event to send messages with
          * @return whether the given tag is unknown to the system
          */
    -    @SuppressWarnings("BooleanMethodNameMustStartWithQuestion")
    -    boolean handleIsUnknownTag(@NotNull String id, @NotNull IReplyCallback event) {
    +    boolean handleIsUnknownTag(String id, IReplyCallback event) {
             if (hasTag(id)) {
                 return false;
             }
    @@ -123,6 +122,7 @@ void putTag(String id, String content) {
          * @param id the id of the tag to get
          * @return the content of the tag, if the tag is known to the system
          */
    +    @Nonnull
         Optional getTag(String id) {
             return database.readTransaction(context -> Optional
                 .ofNullable(context.selectFrom(Tags.TAGS).where(Tags.TAGS.ID.eq(id)).fetchOne())
    @@ -134,6 +134,7 @@ Optional getTag(String id) {
          *
          * @return a set of all ids known to the system, not backed
          */
    +    @Nonnull
         Set getAllIds() {
             return database.readTransaction(context -> context.select(Tags.TAGS.ID)
                 .from(Tags.TAGS)
    diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/tags/TagsCommand.java b/application/src/main/java/org/togetherjava/tjbot/commands/tags/TagsCommand.java
    index 8606c7f933..3432720cb9 100644
    --- a/application/src/main/java/org/togetherjava/tjbot/commands/tags/TagsCommand.java
    +++ b/application/src/main/java/org/togetherjava/tjbot/commands/tags/TagsCommand.java
    @@ -4,7 +4,6 @@
     import net.dv8tion.jda.api.Permission;
     import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
     import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent;
    -import org.jetbrains.annotations.NotNull;
     import org.slf4j.Logger;
     import org.slf4j.LoggerFactory;
     import org.togetherjava.tjbot.commands.SlashCommandAdapter;
    @@ -49,7 +48,7 @@ public TagsCommand(TagSystem tagSystem) {
         }
     
         @Override
    -    public void onSlashCommand(@NotNull SlashCommandInteractionEvent event) {
    +    public void onSlashCommand(SlashCommandInteractionEvent event) {
             Collection tagIds = tagSystem.getAllIds();
             if (tagIds.size() > MAX_TAGS_THRESHOLD_WARNING) {
                 // TODO Implement the edge case
    @@ -73,7 +72,7 @@ public void onSlashCommand(@NotNull SlashCommandInteractionEvent event) {
         }
     
         @Override
    -    public void onButtonClick(@NotNull ButtonInteractionEvent event, @NotNull List args) {
    +    public void onButtonClick(ButtonInteractionEvent event, List args) {
             String userId = args.get(0);
     
             if (!event.getUser().getId().equals(userId) && !Objects.requireNonNull(event.getMember())
    diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/tags/package-info.java b/application/src/main/java/org/togetherjava/tjbot/commands/tags/package-info.java
    index e250c06ae9..41eb96651e 100644
    --- a/application/src/main/java/org/togetherjava/tjbot/commands/tags/package-info.java
    +++ b/application/src/main/java/org/togetherjava/tjbot/commands/tags/package-info.java
    @@ -4,4 +4,7 @@
      * like {@link org.togetherjava.tjbot.commands.tags.TagCommand} as entry point to the package's
      * offered functionality.
      */
    +@ParametersAreNonnullByDefault
     package org.togetherjava.tjbot.commands.tags;
    +
    +import javax.annotation.ParametersAreNonnullByDefault;
    diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/tophelper/TopHelpersCommand.java b/application/src/main/java/org/togetherjava/tjbot/commands/tophelper/TopHelpersCommand.java
    index f7f6a44e4f..5556384cfe 100644
    --- a/application/src/main/java/org/togetherjava/tjbot/commands/tophelper/TopHelpersCommand.java
    +++ b/application/src/main/java/org/togetherjava/tjbot/commands/tophelper/TopHelpersCommand.java
    @@ -10,8 +10,6 @@
     import net.dv8tion.jda.api.interactions.commands.OptionMapping;
     import net.dv8tion.jda.api.interactions.commands.OptionType;
     import net.dv8tion.jda.api.interactions.commands.build.OptionData;
    -import org.jetbrains.annotations.NotNull;
    -import org.jetbrains.annotations.Nullable;
     import org.jooq.Records;
     import org.jooq.impl.DSL;
     import org.slf4j.Logger;
    @@ -20,6 +18,8 @@
     import org.togetherjava.tjbot.commands.SlashCommandVisibility;
     import org.togetherjava.tjbot.db.Database;
     
    +import javax.annotation.Nonnull;
    +import javax.annotation.Nullable;
     import java.math.BigDecimal;
     import java.time.*;
     import java.time.format.TextStyle;
    @@ -50,7 +50,7 @@ public final class TopHelpersCommand extends SlashCommandAdapter {
          *
          * @param database the database containing the message records of top helpers
          */
    -    public TopHelpersCommand(@NotNull Database database) {
    +    public TopHelpersCommand(Database database) {
             super(COMMAND_NAME, "Lists top helpers for the last month, or a given month",
                     SlashCommandVisibility.GUILD);
     
    @@ -65,7 +65,7 @@ public TopHelpersCommand(@NotNull Database database) {
         }
     
         @Override
    -    public void onSlashCommand(@NotNull SlashCommandInteractionEvent event) {
    +    public void onSlashCommand(SlashCommandInteractionEvent event) {
             OptionMapping atMonthData = event.getOption(MONTH_OPTION);
     
             TimeRange timeRange = computeTimeRange(computeMonth(atMonthData));
    @@ -88,7 +88,8 @@ public void onSlashCommand(@NotNull SlashCommandInteractionEvent event) {
                 .onSuccess(members -> handleTopHelpers(topHelpers, members, timeRange, event));
         }
     
    -    private static @NotNull Month computeMonth(@Nullable OptionMapping atMonthData) {
    +    @Nonnull
    +    private static Month computeMonth(@Nullable OptionMapping atMonthData) {
             if (atMonthData != null) {
                 return Month.valueOf(atMonthData.getAsString());
             }
    @@ -97,7 +98,8 @@ public void onSlashCommand(@NotNull SlashCommandInteractionEvent event) {
             return Instant.now().atZone(ZoneOffset.UTC).minusMonths(1).getMonth();
         }
     
    -    private static @NotNull TimeRange computeTimeRange(@NotNull Month atMonth) {
    +    @Nonnull
    +    private static TimeRange computeTimeRange(Month atMonth) {
             ZonedDateTime now = Instant.now().atZone(ZoneOffset.UTC);
     
             int atYear = now.getYear();
    @@ -115,8 +117,8 @@ public void onSlashCommand(@NotNull SlashCommandInteractionEvent event) {
             return new TimeRange(start, end, description);
         }
     
    -    private @NotNull List computeTopHelpersDescending(long guildId,
    -            @NotNull TimeRange timeRange) {
    +    @Nonnull
    +    private List computeTopHelpersDescending(long guildId, TimeRange timeRange) {
             return database.read(context -> context
                 .select(HELP_CHANNEL_MESSAGES.AUTHOR_ID, DSL.sum(HELP_CHANNEL_MESSAGES.MESSAGE_LENGTH))
                 .from(HELP_CHANNEL_MESSAGES)
    @@ -128,14 +130,13 @@ public void onSlashCommand(@NotNull SlashCommandInteractionEvent event) {
                 .fetch(Records.mapping(TopHelperResult::new)));
         }
     
    -    private static void handleError(@NotNull Throwable error, @NotNull IDeferrableCallback event) {
    +    private static void handleError(Throwable error, IDeferrableCallback event) {
             logger.warn("Failed to compute top-helpers", error);
             event.getHook().editOriginal("Sorry, something went wrong.").queue();
         }
     
    -    private static void handleTopHelpers(@NotNull Collection topHelpers,
    -            @NotNull Collection members, @NotNull TimeRange timeRange,
    -            @NotNull IDeferrableCallback event) {
    +    private static void handleTopHelpers(Collection topHelpers,
    +            Collection members, TimeRange timeRange, IDeferrableCallback event) {
             Map userIdToMember =
                     members.stream().collect(Collectors.toMap(Member::getIdLong, Function.identity()));
     
    @@ -150,7 +151,8 @@ private static void handleTopHelpers(@NotNull Collection topHel
             event.getHook().editOriginal(message).queue();
         }
     
    -    private static @NotNull List topHelperToDataRow(@NotNull TopHelperResult topHelper,
    +    @Nonnull
    +    private static List topHelperToDataRow(TopHelperResult topHelper,
                 @Nullable Member member) {
             String id = Long.toString(topHelper.authorId());
             String name = member == null ? "UNKNOWN_USER" : member.getEffectiveName();
    @@ -159,8 +161,9 @@ private static void handleTopHelpers(@NotNull Collection topHel
             return List.of(id, name, messageLengths);
         }
     
    -    private static @NotNull String dataTableToString(@NotNull Collection> dataTable,
    -            @NotNull TimeRange timeRange) {
    +    @Nonnull
    +    private static String dataTableToString(Collection> dataTable,
    +            TimeRange timeRange) {
             return dataTableToAsciiTable(dataTable,
                     List.of(new ColumnSetting("Id", HorizontalAlign.RIGHT),
                             new ColumnSetting("Name", HorizontalAlign.RIGHT),
    @@ -169,9 +172,9 @@ private static void handleTopHelpers(@NotNull Collection topHel
                                     HorizontalAlign.RIGHT)));
         }
     
    -    private static @NotNull String dataTableToAsciiTable(
    -            @NotNull Collection> dataTable,
    -            @NotNull List columnSettings) {
    +    @Nonnull
    +    private static String dataTableToAsciiTable(Collection> dataTable,
    +            List columnSettings) {
             IntFunction headerToAlignment = i -> columnSettings.get(i).headerName();
             IntFunction indexToAlignment = i -> columnSettings.get(i).alignment();
     
    @@ -186,15 +189,14 @@ private static void handleTopHelpers(@NotNull Collection topHel
             return AsciiTable.getTable(AsciiTable.BASIC_ASCII_NO_DATA_SEPARATORS, dataTable, columns);
         }
     
    -    private record TimeRange(@NotNull Instant start, @NotNull Instant end,
    -            @NotNull String description) {
    +    private record TimeRange(Instant start, Instant end, String description) {
         }
     
     
    -    private record TopHelperResult(long authorId, @NotNull BigDecimal messageLengths) {
    +    private record TopHelperResult(long authorId, BigDecimal messageLengths) {
         }
     
     
    -    private record ColumnSetting(@NotNull String headerName, @NotNull HorizontalAlign alignment) {
    +    private record ColumnSetting(String headerName, HorizontalAlign alignment) {
         }
     }
    diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/tophelper/TopHelpersMessageListener.java b/application/src/main/java/org/togetherjava/tjbot/commands/tophelper/TopHelpersMessageListener.java
    index 5d43a1d68e..a363c61898 100644
    --- a/application/src/main/java/org/togetherjava/tjbot/commands/tophelper/TopHelpersMessageListener.java
    +++ b/application/src/main/java/org/togetherjava/tjbot/commands/tophelper/TopHelpersMessageListener.java
    @@ -3,7 +3,6 @@
     import net.dv8tion.jda.api.entities.ChannelType;
     import net.dv8tion.jda.api.entities.ThreadChannel;
     import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
    -import org.jetbrains.annotations.NotNull;
     import org.togetherjava.tjbot.commands.MessageReceiverAdapter;
     import org.togetherjava.tjbot.config.Config;
     import org.togetherjava.tjbot.db.Database;
    @@ -29,7 +28,7 @@ public final class TopHelpersMessageListener extends MessageReceiverAdapter {
          * @param database to store message meta-data in
          * @param config the config to use for this
          */
    -    public TopHelpersMessageListener(@NotNull Database database, @NotNull Config config) {
    +    public TopHelpersMessageListener(Database database, Config config) {
             super(Pattern.compile(".*"));
     
             this.database = database;
    @@ -41,7 +40,7 @@ public TopHelpersMessageListener(@NotNull Database database, @NotNull Config con
         }
     
         @Override
    -    public void onMessageReceived(@NotNull MessageReceivedEvent event) {
    +    public void onMessageReceived(MessageReceivedEvent event) {
             if (event.getAuthor().isBot() || event.isWebhookMessage()) {
                 return;
             }
    @@ -53,7 +52,7 @@ public void onMessageReceived(@NotNull MessageReceivedEvent event) {
             addMessageRecord(event);
         }
     
    -    private boolean isHelpThread(@NotNull MessageReceivedEvent event) {
    +    private boolean isHelpThread(MessageReceivedEvent event) {
             if (event.getChannelType() != ChannelType.GUILD_PUBLIC_THREAD) {
                 return false;
             }
    @@ -64,7 +63,7 @@ private boolean isHelpThread(@NotNull MessageReceivedEvent event) {
                     || isOverviewChannelName.test(rootChannelName);
         }
     
    -    private void addMessageRecord(@NotNull MessageReceivedEvent event) {
    +    private void addMessageRecord(MessageReceivedEvent event) {
             database.write(context -> context.newRecord(HELP_CHANNEL_MESSAGES)
                 .setMessageId(event.getMessage().getIdLong())
                 .setGuildId(event.getGuild().getIdLong())
    diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/tophelper/TopHelpersPurgeMessagesRoutine.java b/application/src/main/java/org/togetherjava/tjbot/commands/tophelper/TopHelpersPurgeMessagesRoutine.java
    index 527c908f1d..a685f1af18 100644
    --- a/application/src/main/java/org/togetherjava/tjbot/commands/tophelper/TopHelpersPurgeMessagesRoutine.java
    +++ b/application/src/main/java/org/togetherjava/tjbot/commands/tophelper/TopHelpersPurgeMessagesRoutine.java
    @@ -1,12 +1,12 @@
     package org.togetherjava.tjbot.commands.tophelper;
     
     import net.dv8tion.jda.api.JDA;
    -import org.jetbrains.annotations.NotNull;
     import org.slf4j.Logger;
     import org.slf4j.LoggerFactory;
     import org.togetherjava.tjbot.commands.Routine;
     import org.togetherjava.tjbot.db.Database;
     
    +import javax.annotation.Nonnull;
     import java.time.Instant;
     import java.time.Period;
     import java.util.concurrent.TimeUnit;
    @@ -28,17 +28,18 @@ public final class TopHelpersPurgeMessagesRoutine implements Routine {
          *
          * @param database the database that contains the messages to purge
          */
    -    public TopHelpersPurgeMessagesRoutine(@NotNull Database database) {
    +    public TopHelpersPurgeMessagesRoutine(Database database) {
             this.database = database;
         }
     
         @Override
    -    public @NotNull Schedule createSchedule() {
    +    @Nonnull
    +    public Schedule createSchedule() {
             return new Schedule(ScheduleMode.FIXED_RATE, 0, 4, TimeUnit.HOURS);
         }
     
         @Override
    -    public void runRoutine(@NotNull JDA jda) {
    +    public void runRoutine(JDA jda) {
             int recordsDeleted =
                     database.writeAndProvide(context -> context.deleteFrom(HELP_CHANNEL_MESSAGES)
                         .where(HELP_CHANNEL_MESSAGES.SENT_AT
    diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/tophelper/package-info.java b/application/src/main/java/org/togetherjava/tjbot/commands/tophelper/package-info.java
    index 61969abb57..224148b934 100644
    --- a/application/src/main/java/org/togetherjava/tjbot/commands/tophelper/package-info.java
    +++ b/application/src/main/java/org/togetherjava/tjbot/commands/tophelper/package-info.java
    @@ -2,4 +2,7 @@
      * This packages offers all the functionality for the top-helpers command system. The core class is
      * {@link org.togetherjava.tjbot.commands.tophelper.TopHelpersCommand}.
      */
    +@ParametersAreNonnullByDefault
     package org.togetherjava.tjbot.commands.tophelper;
    +
    +import javax.annotation.ParametersAreNonnullByDefault;
    diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/utils/DiscordClientAction.java b/application/src/main/java/org/togetherjava/tjbot/commands/utils/DiscordClientAction.java
    index bf3b947e44..0f98a3da4a 100644
    --- a/application/src/main/java/org/togetherjava/tjbot/commands/utils/DiscordClientAction.java
    +++ b/application/src/main/java/org/togetherjava/tjbot/commands/utils/DiscordClientAction.java
    @@ -3,8 +3,8 @@
     import net.dv8tion.jda.api.interactions.components.buttons.Button;
     import net.dv8tion.jda.api.interactions.components.buttons.ButtonStyle;
     import org.jetbrains.annotations.Contract;
    -import org.jetbrains.annotations.NotNull;
     
    +import javax.annotation.Nonnull;
     import java.util.regex.Pattern;
     
     /**
    @@ -261,7 +261,8 @@ public String getRawUrl() {
          * @return The formatted URL as an {@link String}
          * @throws IllegalArgumentException When missing arguments
          */
    -    public String formatUrl(final String @NotNull... arguments) {
    +    @Nonnull
    +    public String formatUrl(final String... arguments) {
             String localUrl = url;
     
             for (final String argument : arguments) {
    @@ -284,11 +285,13 @@ public String formatUrl(final String @NotNull... arguments) {
          * @return A {@link Button} of {@link ButtonStyle#LINK} with the given label
          * @throws IllegalArgumentException When missing arguments
          */
    -    public Button asLinkButton(@NotNull final String label, final String... arguments) {
    +    @Nonnull
    +    public Button asLinkButton(final String label, final String... arguments) {
             return Button.link(formatUrl(arguments), label);
         }
     
         @Override
    +    @Nonnull
         public String toString() {
             return "DiscordClientAction{" + "url='" + url + '\'' + '}';
         }
    diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/utils/Hashing.java b/application/src/main/java/org/togetherjava/tjbot/commands/utils/Hashing.java
    index b019c13864..6b63de1120 100644
    --- a/application/src/main/java/org/togetherjava/tjbot/commands/utils/Hashing.java
    +++ b/application/src/main/java/org/togetherjava/tjbot/commands/utils/Hashing.java
    @@ -1,7 +1,6 @@
     package org.togetherjava.tjbot.commands.utils;
     
    -import org.jetbrains.annotations.NotNull;
    -
    +import javax.annotation.Nonnull;
     import java.nio.charset.StandardCharsets;
     import java.security.MessageDigest;
     import java.security.NoSuchAlgorithmException;
    @@ -26,9 +25,8 @@ private Hashing() {
          * @param bytes the binary data to convert
          * @return a hexadecimal representation
          */
    -    @SuppressWarnings("MagicNumber")
    -    @NotNull
    -    public static String bytesToHex(byte @NotNull [] bytes) {
    +    @Nonnull
    +    public static String bytesToHex(byte[] bytes) {
             Objects.requireNonNull(bytes);
             // See https://stackoverflow.com/a/9855338/2411243
             // noinspection MultiplyOrDivideByPowerOfTwo
    @@ -52,7 +50,8 @@ public static String bytesToHex(byte @NotNull [] bytes) {
          * @param data the data to hash
          * @return the computed hash
          */
    -    public static byte @NotNull [] hash(@NotNull String method, byte @NotNull [] data) {
    +    @Nonnull
    +    public static byte[] hash(String method, byte[] data) {
             Objects.requireNonNull(method);
             Objects.requireNonNull(data);
             try {
    diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/utils/MessageUtils.java b/application/src/main/java/org/togetherjava/tjbot/commands/utils/MessageUtils.java
    index 380b2a50b6..c14a667fe0 100644
    --- a/application/src/main/java/org/togetherjava/tjbot/commands/utils/MessageUtils.java
    +++ b/application/src/main/java/org/togetherjava/tjbot/commands/utils/MessageUtils.java
    @@ -4,8 +4,8 @@
     import net.dv8tion.jda.api.interactions.components.ActionRow;
     import net.dv8tion.jda.api.interactions.components.buttons.Button;
     import net.dv8tion.jda.api.utils.MarkdownSanitizer;
    -import org.jetbrains.annotations.NotNull;
     
    +import javax.annotation.Nonnull;
     import java.util.List;
     
     /**
    @@ -28,7 +28,7 @@ private MessageUtils() {
          * @param message the message that contains at least one button
          * @throws IllegalArgumentException when the given message does not contain any button
          */
    -    public static void disableButtons(@NotNull Message message) {
    +    public static void disableButtons(Message message) {
             List