Skip to content

Commit f809da4

Browse files
authored
ChatGPT Auto-Answer should not be posted when image (#1018)
* Move link detection into separate class * Remove unused method and fix javadocs * Skip chatgpt response if message contains image or link * Fix sonarlint issues * Move fixing encoding issues to its own PR * Remove star import * Move context logic into custom method * Fix reviews * Fix reviews * Rename LinkDetections to LinkDetection
1 parent 793e7c0 commit f809da4

File tree

6 files changed

+114
-51
lines changed

6 files changed

+114
-51
lines changed

application/src/main/java/org/togetherjava/tjbot/features/help/HelpThreadCreatedListener.java

+10-2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
import org.togetherjava.tjbot.features.UserInteractor;
2222
import org.togetherjava.tjbot.features.componentids.ComponentIdGenerator;
2323
import org.togetherjava.tjbot.features.componentids.ComponentIdInteractor;
24+
import org.togetherjava.tjbot.features.utils.LinkDetection;
25+
import org.togetherjava.tjbot.features.utils.MessageUtils;
2426

2527
import java.time.Instant;
2628
import java.time.temporal.ChronoUnit;
@@ -113,8 +115,14 @@ private static boolean isPostedBySelfUser(Message message) {
113115
private RestAction<Message> createAIResponse(ThreadChannel threadChannel) {
114116
RestAction<Message> originalQuestion =
115117
threadChannel.retrieveMessageById(threadChannel.getIdLong());
116-
return originalQuestion.flatMap(message -> helper.constructChatGptAttempt(threadChannel,
117-
getMessageContent(message), componentIdInteractor));
118+
return originalQuestion.flatMap(HelpThreadCreatedListener::isContextSufficient,
119+
message -> helper.constructChatGptAttempt(threadChannel, getMessageContent(message),
120+
componentIdInteractor));
121+
}
122+
123+
private static boolean isContextSufficient(Message message) {
124+
return !MessageUtils.containsImage(message)
125+
&& !LinkDetection.containsLink(message.getContentRaw());
118126
}
119127

120128
private RestAction<Void> pinOriginalQuestion(ThreadChannel threadChannel) {

application/src/main/java/org/togetherjava/tjbot/features/tags/TagCommand.java

+5-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import org.togetherjava.tjbot.features.CommandVisibility;
2020
import org.togetherjava.tjbot.features.SlashCommandAdapter;
21+
import org.togetherjava.tjbot.features.utils.LinkDetection;
2122
import org.togetherjava.tjbot.features.utils.LinkPreview;
2223
import org.togetherjava.tjbot.features.utils.LinkPreviews;
2324
import org.togetherjava.tjbot.features.utils.StringDistances;
@@ -93,7 +94,10 @@ public void onSlashCommand(SlashCommandInteractionEvent event) {
9394
.map(OptionMapping::getAsUser)
9495
.map(User::getAsMention);
9596

96-
List<String> links = LinkPreviews.extractLinks(tagContent)
97+
List<String> links = LinkDetection
98+
.extractLinks(tagContent,
99+
Set.of(LinkDetection.LinkFilter.SUPPRESSED,
100+
LinkDetection.LinkFilter.NON_HTTP_SCHEME))
97101
.stream()
98102
.limit(Message.MAX_EMBED_COUNT - 1L)
99103
.toList();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package org.togetherjava.tjbot.features.utils;
2+
3+
import com.linkedin.urls.Url;
4+
import com.linkedin.urls.detection.UrlDetector;
5+
import com.linkedin.urls.detection.UrlDetectorOptions;
6+
7+
import java.util.List;
8+
import java.util.Optional;
9+
import java.util.Set;
10+
11+
/**
12+
* Utility class to detect links.
13+
*/
14+
public class LinkDetection {
15+
16+
/**
17+
* Possible ways to filter a link.
18+
*
19+
* @see LinkDetection
20+
*/
21+
public enum LinkFilter {
22+
/**
23+
* Filters links suppressed with {@literal <url>}.
24+
*/
25+
SUPPRESSED,
26+
/**
27+
* Filters links that are not using http scheme.
28+
*/
29+
NON_HTTP_SCHEME
30+
}
31+
32+
private LinkDetection() {
33+
throw new UnsupportedOperationException("Utility class");
34+
}
35+
36+
/**
37+
* Extracts all links from the given content.
38+
*
39+
* @param content the content to search through
40+
* @param filter the filters applied to the urls
41+
* @return a list of all found links, can be empty
42+
*/
43+
public static List<String> extractLinks(String content, Set<LinkFilter> filter) {
44+
return new UrlDetector(content, UrlDetectorOptions.BRACKET_MATCH).detect()
45+
.stream()
46+
.map(url -> toLink(url, filter))
47+
.flatMap(Optional::stream)
48+
.toList();
49+
}
50+
51+
/**
52+
* Checks whether the given content contains a link.
53+
*
54+
* @param content the content to search through
55+
* @return true if the content contains at least one link
56+
*/
57+
public static boolean containsLink(String content) {
58+
return !(new UrlDetector(content, UrlDetectorOptions.BRACKET_MATCH).detect().isEmpty());
59+
}
60+
61+
private static Optional<String> toLink(Url url, Set<LinkFilter> filter) {
62+
String raw = url.getOriginalUrl();
63+
if (filter.contains(LinkFilter.SUPPRESSED) && raw.contains(">")) {
64+
// URL escapes, such as "<http://example.com>" should be skipped
65+
return Optional.empty();
66+
}
67+
// Not interested in other schemes, also to filter out matches without scheme.
68+
// It detects a lot of such false-positives in Java snippets
69+
if (filter.contains(LinkFilter.NON_HTTP_SCHEME) && !raw.startsWith("http")) {
70+
return Optional.empty();
71+
}
72+
73+
String link = url.getFullUrl();
74+
75+
if (link.endsWith(",") || link.endsWith(".")) {
76+
// Remove trailing punctuation
77+
link = link.substring(0, link.length() - 1);
78+
}
79+
80+
return Optional.of(link);
81+
}
82+
83+
}

application/src/main/java/org/togetherjava/tjbot/features/utils/LinkPreviews.java

+2-40
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
package org.togetherjava.tjbot.features.utils;
22

3-
import com.linkedin.urls.Url;
4-
import com.linkedin.urls.detection.UrlDetector;
5-
import com.linkedin.urls.detection.UrlDetectorOptions;
63
import org.jsoup.Jsoup;
74
import org.jsoup.nodes.Document;
85
import org.slf4j.Logger;
@@ -27,7 +24,8 @@
2724
import java.util.stream.IntStream;
2825

2926
/**
30-
* Provides means to create previews of links. See {@link #extractLinks(String)} and
27+
* Provides means to create previews of links. See
28+
* {@link LinkDetection#extractLinks(String, boolean, boolean)} and
3129
* {@link #createLinkPreviews(List)}.
3230
*/
3331
public final class LinkPreviews {
@@ -43,42 +41,6 @@ private LinkPreviews() {
4341
throw new UnsupportedOperationException("Utility class");
4442
}
4543

46-
/**
47-
* Extracts all links from the given content.
48-
*
49-
* @param content the content to search through
50-
* @return a list of all found links, can be empty
51-
*/
52-
public static List<String> extractLinks(String content) {
53-
return new UrlDetector(content, UrlDetectorOptions.BRACKET_MATCH).detect()
54-
.stream()
55-
.map(LinkPreviews::toLink)
56-
.flatMap(Optional::stream)
57-
.toList();
58-
}
59-
60-
private static Optional<String> toLink(Url url) {
61-
String raw = url.getOriginalUrl();
62-
if (raw.contains(">")) {
63-
// URL escapes, such as "<http://example.com>" should be skipped
64-
return Optional.empty();
65-
}
66-
// Not interested in other schemes, also to filter out matches without scheme.
67-
// It detects a lot of such false-positives in Java snippets
68-
if (!raw.startsWith("http")) {
69-
return Optional.empty();
70-
}
71-
72-
String link = url.getFullUrl();
73-
74-
if (link.endsWith(",") || link.endsWith(".")) {
75-
// Remove trailing punctuation
76-
link = link.substring(0, link.length() - 1);
77-
}
78-
79-
return Optional.of(link);
80-
}
81-
8244
/**
8345
* Attempts to create previews of all given links.
8446
* <p>

application/src/main/java/org/togetherjava/tjbot/features/utils/MessageUtils.java

+14
Original file line numberDiff line numberDiff line change
@@ -219,4 +219,18 @@ public static Optional<CodeFence> extractCode(String fullMessage) {
219219

220220
return Optional.of(new CodeFence(language, code));
221221
}
222+
223+
/**
224+
* Checks if a given message contains any image attachments.
225+
*
226+
* @param message the message to be checked
227+
* @return {@code true} if the message contains at least one image attachment
228+
*
229+
* @see Message
230+
* @see Message.Attachment
231+
*/
232+
public static boolean containsImage(Message message) {
233+
return message.getAttachments().stream().anyMatch(Message.Attachment::isImage);
234+
}
235+
222236
}

build.gradle

-8
Original file line numberDiff line numberDiff line change
@@ -86,12 +86,4 @@ subprojects {
8686
test {
8787
useJUnitPlatform()
8888
}
89-
90-
compileJava {
91-
options.encoding = "UTF-8"
92-
}
93-
94-
compileTestJava {
95-
options.encoding = "UTF-8"
96-
}
9789
}

0 commit comments

Comments
 (0)