diff --git a/.github/workflows/basic-checks.yml b/.github/workflows/basic-checks.yml index 494cd7a47e..12e467306c 100644 --- a/.github/workflows/basic-checks.yml +++ b/.github/workflows/basic-checks.yml @@ -3,7 +3,7 @@ name: Basic checks on: [pull_request] env: - JAVA_VERSION: 17 + JAVA_VERSION: 18 jobs: spotless: diff --git a/.github/workflows/code-analysis.yml b/.github/workflows/code-analysis.yml index a56a4e5d29..e10ae6db1c 100644 --- a/.github/workflows/code-analysis.yml +++ b/.github/workflows/code-analysis.yml @@ -8,7 +8,7 @@ on: - cron: '0 20 * * 4' env: - JAVA_VERSION: 17 + JAVA_VERSION: 18 jobs: sonar: diff --git a/.github/workflows/docker-publish.yaml b/.github/workflows/docker-publish.yaml index 6f536c82c9..b6d354a175 100644 --- a/.github/workflows/docker-publish.yaml +++ b/.github/workflows/docker-publish.yaml @@ -7,7 +7,7 @@ on: - 'master' env: - JAVA_VERSION: 17 + JAVA_VERSION: 18 jobs: docker: diff --git a/.github/workflows/docker-verify.yaml b/.github/workflows/docker-verify.yaml index a07bb8a9e9..be8e970de9 100644 --- a/.github/workflows/docker-verify.yaml +++ b/.github/workflows/docker-verify.yaml @@ -3,7 +3,7 @@ name: Docker Verify on: [pull_request] env: - JAVA_VERSION: 17 + JAVA_VERSION: 18 jobs: docker: diff --git a/.github/workflows/releases.yaml b/.github/workflows/releases.yaml index 69edb9ecc9..7b44328b5d 100644 --- a/.github/workflows/releases.yaml +++ b/.github/workflows/releases.yaml @@ -10,7 +10,7 @@ defaults: shell: bash env: - JAVA_VERSION: 17 + JAVA_VERSION: 18 jobs: diff --git a/README.md b/README.md index f0982139ea..cebc069fa1 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # TJ-Bot [![codefactor](https://img.shields.io/codefactor/grade/github/together-java/tj-bot)](https://www.codefactor.io/repository/github/together-java/tj-bot) -![Java](https://img.shields.io/badge/Java-17%2B-ff696c) +![Java](https://img.shields.io/badge/Java-18-ff696c) [![license](https://img.shields.io/github/license/Together-Java/TJ-Bot)](https://github.com/Together-Java/TJ-Bot/blob/master/LICENSE) ![GitHub release (latest by date)](https://img.shields.io/github/v/release/Together-Java/TJ-Bot?label=release) @@ -28,4 +28,4 @@ Head over to the [Wiki](https://github.com/Together-Java/TJ-Bot/wiki) as general Jar downloads are available from the [release section](https://github.com/Together-Java/TJ-Bot/releases). -Alternatively, you can also download the project using your favorite build tool. Artifacts are made available via https://jitpack.io. Our `groupId` is `com.github.Together-Java`, the `artifactId` is `TJ-Bot`. \ No newline at end of file +Alternatively, you can also download the project using your favorite build tool. Artifacts are made available via https://jitpack.io. Our `groupId` is `com.github.Together-Java`, the `artifactId` is `TJ-Bot`. diff --git a/application/build.gradle b/application/build.gradle index c448842157..e080e1b7d7 100644 --- a/application/build.gradle +++ b/application/build.gradle @@ -18,7 +18,7 @@ repositories { var outputImage = 'togetherjava.duckdns.org:5001/togetherjava/tjbot:' + System.getenv('BRANCH_NAME') ?: 'latest' jib { - from.image = 'eclipse-temurin:17' + from.image = 'eclipse-temurin:18' to { image = outputImage auth { 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 e3d7b5b29a..280f32599e 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 @@ -22,6 +22,7 @@ */ public final class SuggestionsUpDownVoter extends MessageReceiverAdapter { private static final Logger logger = LoggerFactory.getLogger(SuggestionsUpDownVoter.class); + private static final int TITLE_MAX_LENGTH = 60; private static final String FALLBACK_UP_VOTE = "👍"; private static final String FALLBACK_DOWN_VOTE = "👎"; @@ -47,10 +48,27 @@ public void onMessageReceived(@NotNull MessageReceivedEvent event) { Guild guild = event.getGuild(); Message message = event.getMessage(); + createThread(message); reactWith(config.getUpVoteEmoteName(), FALLBACK_UP_VOTE, guild, message); reactWith(config.getDownVoteEmoteName(), FALLBACK_DOWN_VOTE, guild, message); } + private static void createThread(@NotNull Message message) { + String title = message.getContentRaw(); + + if (title.length() >= TITLE_MAX_LENGTH) { + int lastWordEnd = title.lastIndexOf(' ', TITLE_MAX_LENGTH); + + if (lastWordEnd == -1) { + lastWordEnd = TITLE_MAX_LENGTH; + } + + title = title.substring(0, lastWordEnd); + } + + message.createThreadChannel(title).queue(); + } + private static void reactWith(@NotNull String emoteName, @NotNull String fallbackUnicodeEmote, @NotNull Guild guild, @NotNull Message message) { getEmoteByName(emoteName, guild).map(message::addReaction).orElseGet(() -> { 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 3d9f72270b..63f0b23951 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 @@ -3,12 +3,12 @@ import net.dv8tion.jda.api.entities.*; import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import net.dv8tion.jda.api.exceptions.ErrorResponseException; +import net.dv8tion.jda.api.interactions.InteractionHook; 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 net.dv8tion.jda.api.requests.ErrorResponse; import net.dv8tion.jda.api.requests.RestAction; -import net.dv8tion.jda.api.requests.restaction.interactions.ReplyCallbackAction; import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -92,11 +92,16 @@ public void onSlashCommand(@NotNull SlashCommandInteractionEvent event) { } TextChannel overviewChannel = maybeOverviewChannel.orElseThrow(); + InteractionHook eventHook = event.getHook(); + Member author = event.getMember(); + Guild guild = event.getGuild(); + event.deferReply(true).queue(); + overviewChannel.createThreadChannel("[%s] %s".formatted(category, title)) - .flatMap(threadChannel -> handleEvent(event, threadChannel, event.getMember(), title, - category)) + .flatMap(threadChannel -> handleEvent(eventHook, threadChannel, author, title, category, + guild)) .queue(any -> { - }, e -> handleFailure(e, event)); + }, e -> handleFailure(e, eventHook)); } private boolean handleIsStagingChannel(@NotNull IReplyCallback event) { @@ -125,11 +130,11 @@ private boolean handleIsValidTitle(@NotNull CharSequence title, @NotNull IReplyC return false; } - private @NotNull RestAction handleEvent(@NotNull IReplyCallback event, + private @NotNull RestAction handleEvent(@NotNull InteractionHook eventHook, @NotNull ThreadChannel threadChannel, @NotNull Member author, @NotNull String title, - @NotNull String category) { - return sendInitialMessage(event.getGuild(), threadChannel, author, title, category) - .flatMap(any -> notifyUser(event, threadChannel)) + @NotNull String category, @NotNull Guild guild) { + return sendInitialMessage(guild, threadChannel, author, title, category) + .flatMap(any -> notifyUser(eventHook, threadChannel)) .flatMap(any -> helper.sendExplanationMessage(threadChannel)); } @@ -153,22 +158,21 @@ private RestAction sendInitialMessage(@NotNull Guild guild, .flatMap(message -> message.editMessage(contentWithRole)); } - private static @NotNull ReplyCallbackAction notifyUser(@NotNull IReplyCallback event, + private static @NotNull RestAction notifyUser(@NotNull InteractionHook eventHook, @NotNull IMentionable threadChannel) { - return event.reply(""" + return eventHook.editOriginal(""" Created a thread for you: %s - Please ask your question there, thanks.""".formatted(threadChannel.getAsMention())) - .setEphemeral(true); + Please ask your question there, thanks.""".formatted(threadChannel.getAsMention())); } - private static void handleFailure(@NotNull Throwable exception, @NotNull IReplyCallback event) { + private static void handleFailure(@NotNull Throwable exception, + @NotNull InteractionHook eventHook) { if (exception instanceof ErrorResponseException responseException) { ErrorResponse response = responseException.getErrorResponse(); if (response == ErrorResponse.MAX_CHANNELS || response == ErrorResponse.MAX_ACTIVE_THREADS) { - event.reply( + eventHook.editOriginal( "It seems that there are currently too many active questions, please try again in a few minutes.") - .setEphemeral(true) .queue(); return; } 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 a94471c103..14a6d0cb5b 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 @@ -4,6 +4,7 @@ import com.github.benmanes.caffeine.cache.Caffeine; import net.dv8tion.jda.api.entities.ThreadChannel; 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; @@ -15,6 +16,9 @@ import java.util.Optional; import java.util.concurrent.TimeUnit; +import static org.togetherjava.tjbot.commands.help.HelpSystemHelper.TITLE_COMPACT_LENGTH_MAX; +import static org.togetherjava.tjbot.commands.help.HelpSystemHelper.TITLE_COMPACT_LENGTH_MIN; + /** * Implements the {@code /change-help-title} command, which is able to change the title of a help * thread. @@ -54,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)) { + if (!helper.handleIsHelpThread(event) || !handleIsValidTitle(title, event)) { return; } @@ -88,4 +92,18 @@ private boolean isHelpThreadOnCooldown(@NotNull ThreadChannel helpThread) { .filter(Instant.now()::isBefore) .isPresent(); } + + private boolean handleIsValidTitle(@NotNull CharSequence title, @NotNull IReplyCallback event) { + if (HelpSystemHelper.isTitleValid(title)) { + 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)) + .setEphemeral(true) + .queue(); + + return false; + } } 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 b7f5e77f1a..c6bbd74544 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 @@ -36,7 +36,7 @@ public final class HelpSystemHelper { private static final Pattern TITLE_COMPACT_REMOVAL_PATTERN = Pattern.compile("\\W"); static final int TITLE_COMPACT_LENGTH_MIN = 2; - static final int TITLE_COMPACT_LENGTH_MAX = 80; + static final int TITLE_COMPACT_LENGTH_MAX = 70; private final Predicate isOverviewChannelName; private final String overviewChannelPattern; 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 b568a2e0d2..910b7c8663 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 @@ -26,7 +26,7 @@ public record ActionRecord(int caseId, @NotNull Instant issuedAt, long guildId, /** * Creates the action record that corresponds to the given action entry from the database table. - * + * * @param action the action to convert * @return the corresponding action record */ @@ -37,4 +37,14 @@ public record ActionRecord(int caseId, @NotNull Instant issuedAt, long guildId, ModerationAction.valueOf(action.getActionType()), action.getActionExpiresAt(), action.getReason()); } + + /** + * Whether this action is still effective. That is, it is either a permanent action or temporary + * but not expired yet. + * + * @return True when still effective, false otherwise + */ + public boolean isEffective() { + return actionExpiresAt == null || actionExpiresAt.isAfter(Instant.now()); + } } 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 d2822c65f5..2d306f0247 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 @@ -12,7 +12,6 @@ import org.togetherjava.tjbot.commands.EventReceiver; import org.togetherjava.tjbot.config.Config; -import java.time.Instant; import java.util.List; import java.util.Optional; import java.util.function.Function; @@ -82,23 +81,18 @@ private boolean shouldApplyModerationRole(@NotNull ModerationRole moderationRole member.getGuild().getIdLong(), member.getIdLong(), moderationRole.revokeAction); if (lastRevokeAction.isEmpty()) { // User was never e.g. unmuted - return isActionEffective(lastApplyAction.orElseThrow()); + return lastApplyAction.orElseThrow().isEffective(); } // The last issued action takes priority if (lastApplyAction.orElseThrow() .issuedAt() .isAfter(lastRevokeAction.orElseThrow().issuedAt())) { - return isActionEffective(lastApplyAction.orElseThrow()); + return lastApplyAction.orElseThrow().isEffective(); } return false; } - private static boolean isActionEffective(@NotNull ActionRecord action) { - // Effective if permanent or expires in the future - return action.actionExpiresAt() == null || action.actionExpiresAt().isAfter(Instant.now()); - } - private static void applyModerationRole(@NotNull ModerationRole moderationRole, @NotNull Member member) { Guild guild = member.getGuild(); 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 26671c196e..78e393e3b0 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 @@ -13,7 +13,6 @@ import org.togetherjava.tjbot.commands.moderation.ModerationActionsStore; import org.togetherjava.tjbot.config.Config; -import java.time.Instant; import java.util.Map; import java.util.Objects; import java.util.Optional; @@ -80,14 +79,14 @@ private void checkExpiredActions() { } private void processGroupedActions(@NotNull RevocationGroupIdentifier groupIdentifier) { - // Do not revoke an action which was overwritten by a permanent action that was issued + // 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 ActionRecord lastApplyAction = actionsStore .findLastActionAgainstTargetByType(groupIdentifier.guildId, groupIdentifier.targetId, groupIdentifier.type) .orElseThrow(); - if (lastApplyAction.actionExpiresAt() == null) { + if (lastApplyAction.isEffective()) { return; } @@ -101,8 +100,7 @@ private void processGroupedActions(@NotNull RevocationGroupIdentifier groupIdent if (lastRevokeActionOpt.isPresent()) { ActionRecord lastRevokeAction = lastRevokeActionOpt.orElseThrow(); if (lastRevokeAction.issuedAt().isAfter(lastApplyAction.issuedAt()) - && (lastRevokeAction.actionExpiresAt() == null - || lastRevokeAction.actionExpiresAt().isAfter(Instant.now()))) { + && lastRevokeAction.isEffective()) { return; } } diff --git a/build.gradle b/build.gradle index b42322463b..46b1ad685f 100644 --- a/build.gradle +++ b/build.gradle @@ -63,8 +63,8 @@ subprojects { options.encoding = 'UTF-8' // Nails the Java-Version of every Subproject - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 + sourceCompatibility = JavaVersion.VERSION_18 + targetCompatibility = JavaVersion.VERSION_18 } compileJava(compileTasks) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index e750102e09..8396279267 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-rc-5-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists