diff --git a/application/src/main/java/org/togetherjava/tjbot/features/help/HelpThreadCreatedListener.java b/application/src/main/java/org/togetherjava/tjbot/features/help/HelpThreadCreatedListener.java index 7fade90333..42ef619e53 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/help/HelpThreadCreatedListener.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/help/HelpThreadCreatedListener.java @@ -21,6 +21,8 @@ import org.togetherjava.tjbot.features.UserInteractor; import org.togetherjava.tjbot.features.componentids.ComponentIdGenerator; import org.togetherjava.tjbot.features.componentids.ComponentIdInteractor; +import org.togetherjava.tjbot.features.utils.LinkDetection; +import org.togetherjava.tjbot.features.utils.MessageUtils; import java.time.Instant; import java.time.temporal.ChronoUnit; @@ -113,8 +115,14 @@ private static boolean isPostedBySelfUser(Message message) { private RestAction createAIResponse(ThreadChannel threadChannel) { RestAction originalQuestion = threadChannel.retrieveMessageById(threadChannel.getIdLong()); - return originalQuestion.flatMap(message -> helper.constructChatGptAttempt(threadChannel, - getMessageContent(message), componentIdInteractor)); + return originalQuestion.flatMap(HelpThreadCreatedListener::isContextSufficient, + message -> helper.constructChatGptAttempt(threadChannel, getMessageContent(message), + componentIdInteractor)); + } + + private static boolean isContextSufficient(Message message) { + return !MessageUtils.containsImage(message) + && !LinkDetection.containsLink(message.getContentRaw()); } private RestAction pinOriginalQuestion(ThreadChannel threadChannel) { diff --git a/application/src/main/java/org/togetherjava/tjbot/features/tags/TagCommand.java b/application/src/main/java/org/togetherjava/tjbot/features/tags/TagCommand.java index b9c5d76aa9..2cf1994fba 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/tags/TagCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/tags/TagCommand.java @@ -18,6 +18,7 @@ import org.togetherjava.tjbot.features.CommandVisibility; import org.togetherjava.tjbot.features.SlashCommandAdapter; +import org.togetherjava.tjbot.features.utils.LinkDetection; import org.togetherjava.tjbot.features.utils.LinkPreview; import org.togetherjava.tjbot.features.utils.LinkPreviews; import org.togetherjava.tjbot.features.utils.StringDistances; @@ -93,7 +94,10 @@ public void onSlashCommand(SlashCommandInteractionEvent event) { .map(OptionMapping::getAsUser) .map(User::getAsMention); - List links = LinkPreviews.extractLinks(tagContent) + List links = LinkDetection + .extractLinks(tagContent, + Set.of(LinkDetection.LinkFilter.SUPPRESSED, + LinkDetection.LinkFilter.NON_HTTP_SCHEME)) .stream() .limit(Message.MAX_EMBED_COUNT - 1L) .toList(); diff --git a/application/src/main/java/org/togetherjava/tjbot/features/utils/LinkDetection.java b/application/src/main/java/org/togetherjava/tjbot/features/utils/LinkDetection.java new file mode 100644 index 0000000000..3b6dc18112 --- /dev/null +++ b/application/src/main/java/org/togetherjava/tjbot/features/utils/LinkDetection.java @@ -0,0 +1,83 @@ +package org.togetherjava.tjbot.features.utils; + +import com.linkedin.urls.Url; +import com.linkedin.urls.detection.UrlDetector; +import com.linkedin.urls.detection.UrlDetectorOptions; + +import java.util.List; +import java.util.Optional; +import java.util.Set; + +/** + * Utility class to detect links. + */ +public class LinkDetection { + + /** + * Possible ways to filter a link. + * + * @see LinkDetection + */ + public enum LinkFilter { + /** + * Filters links suppressed with {@literal }. + */ + SUPPRESSED, + /** + * Filters links that are not using http scheme. + */ + NON_HTTP_SCHEME + } + + private LinkDetection() { + throw new UnsupportedOperationException("Utility class"); + } + + /** + * Extracts all links from the given content. + * + * @param content the content to search through + * @param filter the filters applied to the urls + * @return a list of all found links, can be empty + */ + public static List extractLinks(String content, Set filter) { + return new UrlDetector(content, UrlDetectorOptions.BRACKET_MATCH).detect() + .stream() + .map(url -> toLink(url, filter)) + .flatMap(Optional::stream) + .toList(); + } + + /** + * Checks whether the given content contains a link. + * + * @param content the content to search through + * @return true if the content contains at least one link + */ + public static boolean containsLink(String content) { + return !(new UrlDetector(content, UrlDetectorOptions.BRACKET_MATCH).detect().isEmpty()); + } + + private static Optional toLink(Url url, Set filter) { + String raw = url.getOriginalUrl(); + if (filter.contains(LinkFilter.SUPPRESSED) && raw.contains(">")) { + // URL escapes, such as "" should be skipped + return Optional.empty(); + } + // Not interested in other schemes, also to filter out matches without scheme. + // It detects a lot of such false-positives in Java snippets + if (filter.contains(LinkFilter.NON_HTTP_SCHEME) && !raw.startsWith("http")) { + return Optional.empty(); + } + + String link = url.getFullUrl(); + + if (link.endsWith(",") || link.endsWith(".")) { + // Remove trailing punctuation + link = link.substring(0, link.length() - 1); + } + + return Optional.of(link); + } + +} diff --git a/application/src/main/java/org/togetherjava/tjbot/features/utils/LinkPreviews.java b/application/src/main/java/org/togetherjava/tjbot/features/utils/LinkPreviews.java index f5bbb5f353..cc84fa3083 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/utils/LinkPreviews.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/utils/LinkPreviews.java @@ -1,8 +1,5 @@ package org.togetherjava.tjbot.features.utils; -import com.linkedin.urls.Url; -import com.linkedin.urls.detection.UrlDetector; -import com.linkedin.urls.detection.UrlDetectorOptions; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.slf4j.Logger; @@ -27,7 +24,8 @@ import java.util.stream.IntStream; /** - * Provides means to create previews of links. See {@link #extractLinks(String)} and + * Provides means to create previews of links. See + * {@link LinkDetection#extractLinks(String, boolean, boolean)} and * {@link #createLinkPreviews(List)}. */ public final class LinkPreviews { @@ -43,42 +41,6 @@ private LinkPreviews() { throw new UnsupportedOperationException("Utility class"); } - /** - * Extracts all links from the given content. - * - * @param content the content to search through - * @return a list of all found links, can be empty - */ - public static List extractLinks(String content) { - return new UrlDetector(content, UrlDetectorOptions.BRACKET_MATCH).detect() - .stream() - .map(LinkPreviews::toLink) - .flatMap(Optional::stream) - .toList(); - } - - private static Optional toLink(Url url) { - String raw = url.getOriginalUrl(); - if (raw.contains(">")) { - // URL escapes, such as "" should be skipped - return Optional.empty(); - } - // Not interested in other schemes, also to filter out matches without scheme. - // It detects a lot of such false-positives in Java snippets - if (!raw.startsWith("http")) { - return Optional.empty(); - } - - String link = url.getFullUrl(); - - if (link.endsWith(",") || link.endsWith(".")) { - // Remove trailing punctuation - link = link.substring(0, link.length() - 1); - } - - return Optional.of(link); - } - /** * Attempts to create previews of all given links. *

diff --git a/application/src/main/java/org/togetherjava/tjbot/features/utils/MessageUtils.java b/application/src/main/java/org/togetherjava/tjbot/features/utils/MessageUtils.java index 661f5acd64..cb04c9e634 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/utils/MessageUtils.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/utils/MessageUtils.java @@ -219,4 +219,18 @@ public static Optional extractCode(String fullMessage) { return Optional.of(new CodeFence(language, code)); } + + /** + * Checks if a given message contains any image attachments. + * + * @param message the message to be checked + * @return {@code true} if the message contains at least one image attachment + * + * @see Message + * @see Message.Attachment + */ + public static boolean containsImage(Message message) { + return message.getAttachments().stream().anyMatch(Message.Attachment::isImage); + } + } diff --git a/build.gradle b/build.gradle index 070ce9febe..12857fc695 100644 --- a/build.gradle +++ b/build.gradle @@ -86,12 +86,4 @@ subprojects { test { useJUnitPlatform() } - - compileJava { - options.encoding = "UTF-8" - } - - compileTestJava { - options.encoding = "UTF-8" - } }