-
-
Notifications
You must be signed in to change notification settings - Fork 92
Gist auto filesharing in help threads #491
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
20 commits
Select commit
Hold shift + click to select a range
b393ed4
initial commit
SquidXTV 5231378
initial commit
SquidXTV 91a19a4
delete
SquidXTV d7b4b1b
worked on PR comments
SquidXTV 366d115
added api key
SquidXTV a65e584
Added default api key to config
SquidXTV 9285fdc
fixed api key
SquidXTV 9012162
changed to record
SquidXTV e3dcc52
removed comments
SquidXTV c0b8872
added multiple uploads to one gist
SquidXTV 67f48d1
Worked on pr comments
SquidXTV 49054ac
Fixed Sonarlint checks
SquidXTV 764380f
Added documentation
SquidXTV a58dde3
Worked on pr comments
SquidXTV 66614b6
Accidentally pushed
SquidXTV 85d5f2a
pr comments
SquidXTV 045d748
pr comments
SquidXTV 012085e
pr comments
SquidXTV 641299c
pr comments
SquidXTV 0a366ef
pr comments
SquidXTV File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
213 changes: 213 additions & 0 deletions
213
...src/main/java/org/togetherjava/tjbot/commands/filesharing/FileSharingMessageListener.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,213 @@ | ||
package org.togetherjava.tjbot.commands.filesharing; | ||
|
||
import com.fasterxml.jackson.core.JsonProcessingException; | ||
import com.fasterxml.jackson.databind.ObjectMapper; | ||
import net.dv8tion.jda.api.entities.ChannelType; | ||
import net.dv8tion.jda.api.entities.Message; | ||
import net.dv8tion.jda.api.entities.ThreadChannel; | ||
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 java.io.IOException; | ||
import java.io.InputStream; | ||
import java.io.UncheckedIOException; | ||
import java.net.HttpURLConnection; | ||
import java.net.URI; | ||
import java.net.http.HttpClient; | ||
import java.net.http.HttpRequest; | ||
import java.net.http.HttpResponse; | ||
import java.nio.charset.StandardCharsets; | ||
import java.util.*; | ||
import java.util.concurrent.CompletableFuture; | ||
import java.util.concurrent.ConcurrentHashMap; | ||
import java.util.function.Predicate; | ||
import java.util.regex.Pattern; | ||
|
||
/** | ||
* Listener that receives all sent help messages and uploads them to a share service if the message | ||
* contains a file with the given extension in the | ||
* {@link FileSharingMessageListener#extensionFilter}. | ||
*/ | ||
public class FileSharingMessageListener extends MessageReceiverAdapter { | ||
|
||
private static final Logger LOGGER = LoggerFactory.getLogger(FileSharingMessageListener.class); | ||
private static final ObjectMapper JSON = new ObjectMapper(); | ||
|
||
private static final String SHARE_API = "https://api.github.com/gists"; | ||
private static final HttpClient CLIENT = HttpClient.newHttpClient(); | ||
|
||
private final String gistApiKey; | ||
private final Set<String> extensionFilter = Set.of("txt", "java", "gradle", "xml", "kt", "json", | ||
"fxml", "css", "c", "h", "cpp", "py", "yml"); | ||
|
||
private final Predicate<String> isStagingChannelName; | ||
private final Predicate<String> isOverviewChannelName; | ||
|
||
|
||
public FileSharingMessageListener(@NotNull Config config) { | ||
super(Pattern.compile(".*")); | ||
|
||
gistApiKey = config.getGistApiKey(); | ||
isStagingChannelName = Pattern.compile(config.getHelpSystem().getStagingChannelPattern()) | ||
.asMatchPredicate(); | ||
isOverviewChannelName = Pattern.compile(config.getHelpSystem().getOverviewChannelPattern()) | ||
.asMatchPredicate(); | ||
} | ||
|
||
@Override | ||
public void onMessageReceived(@NotNull MessageReceivedEvent event) { | ||
User author = event.getAuthor(); | ||
if (author.isBot() || event.isWebhookMessage()) { | ||
return; | ||
} | ||
|
||
if (!isHelpThread(event)) { | ||
return; | ||
} | ||
Taz03 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
|
||
List<Message.Attachment> attachments = event.getMessage() | ||
.getAttachments() | ||
.stream() | ||
.filter(this::isAttachmentRelevant) | ||
.toList(); | ||
|
||
CompletableFuture.runAsync(() -> { | ||
try { | ||
processAttachments(event, attachments); | ||
} catch (Exception e) { | ||
LOGGER.error("Unknown error while processing attachments", e); | ||
} | ||
}); | ||
} | ||
|
||
private boolean isAttachmentRelevant(@NotNull Message.Attachment attachment) { | ||
String extension = attachment.getFileExtension(); | ||
if (extension == null) { | ||
return false; | ||
} | ||
return extensionFilter.contains(extension); | ||
Zabuzard marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
|
||
private void processAttachments(@NotNull MessageReceivedEvent event, | ||
@NotNull List<Message.Attachment> attachments) { | ||
|
||
Map<String, GistFile> nameToFile = new ConcurrentHashMap<>(); | ||
|
||
List<CompletableFuture<Void>> tasks = new ArrayList<>(); | ||
for (Message.Attachment attachment : attachments) { | ||
CompletableFuture<Void> task = attachment.retrieveInputStream() | ||
.thenApply(this::readAttachment) | ||
.thenAccept( | ||
content -> nameToFile.put(getNameOf(attachment), new GistFile(content))); | ||
|
||
tasks.add(task); | ||
} | ||
|
||
tasks.forEach(CompletableFuture::join); | ||
|
||
GistFiles files = new GistFiles(nameToFile); | ||
GistRequest request = new GistRequest(event.getAuthor().getName(), false, files); | ||
String url = uploadToGist(request); | ||
sendResponse(event, url); | ||
} | ||
|
||
private @NotNull String readAttachment(@NotNull InputStream stream) { | ||
try { | ||
return new String(stream.readAllBytes(), StandardCharsets.UTF_8); | ||
} catch (IOException e) { | ||
throw new UncheckedIOException(e); | ||
} | ||
} | ||
|
||
private @NotNull String getNameOf(@NotNull Message.Attachment attachment) { | ||
String fileName = attachment.getFileName(); | ||
String fileExtension = attachment.getFileExtension(); | ||
|
||
if (fileExtension == null || fileExtension.equals("txt")) { | ||
fileExtension = "java"; | ||
} else if (fileExtension.equals("fxml")) { | ||
fileExtension = "xml"; | ||
} | ||
|
||
int extensionIndex = fileName.lastIndexOf('.'); | ||
if (extensionIndex != -1) { | ||
fileName = fileName.substring(0, extensionIndex); | ||
} | ||
|
||
fileName += "." + fileExtension; | ||
|
||
return fileName; | ||
} | ||
|
||
private @NotNull String uploadToGist(@NotNull GistRequest jsonRequest) { | ||
String body; | ||
try { | ||
body = JSON.writeValueAsString(jsonRequest); | ||
} catch (JsonProcessingException e) { | ||
throw new IllegalStateException( | ||
"Attempting to upload a file to gist, but unable to create the JSON request.", | ||
e); | ||
} | ||
|
||
HttpRequest request = HttpRequest.newBuilder() | ||
.uri(URI.create(SHARE_API)) | ||
.header("Accept", "application/json") | ||
.header("Authorization", "token " + gistApiKey) | ||
SquidXTV marked this conversation as resolved.
Show resolved
Hide resolved
|
||
.POST(HttpRequest.BodyPublishers.ofString(body)) | ||
.build(); | ||
|
||
HttpResponse<String> apiResponse; | ||
try { | ||
apiResponse = CLIENT.send(request, HttpResponse.BodyHandlers.ofString()); | ||
SquidXTV marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} catch (IOException e) { | ||
throw new UncheckedIOException(e); | ||
} catch (InterruptedException e) { | ||
Thread.currentThread().interrupt(); | ||
throw new IllegalStateException( | ||
"Attempting to upload a file to gist, but the request got interrupted.", e); | ||
} | ||
|
||
int statusCode = apiResponse.statusCode(); | ||
|
||
if (statusCode < HttpURLConnection.HTTP_OK | ||
|| statusCode >= HttpURLConnection.HTTP_MULT_CHOICE) { | ||
throw new IllegalStateException("Gist API unexpected response: " + apiResponse.body()); | ||
} | ||
|
||
GistResponse gistResponse; | ||
try { | ||
gistResponse = JSON.readValue(apiResponse.body(), GistResponse.class); | ||
} catch (JsonProcessingException e) { | ||
throw new IllegalStateException( | ||
"Attempting to upload file to gist, but unable to parse its JSON response.", e); | ||
} | ||
return gistResponse.getHtmlUrl(); | ||
} | ||
|
||
private void sendResponse(@NotNull MessageReceivedEvent event, @NotNull 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 👍"; | ||
|
||
message.reply(messageContent).setActionRow(Button.link(url, "gist")).queue(); | ||
} | ||
|
||
private boolean isHelpThread(@NotNull MessageReceivedEvent event) { | ||
if (event.getChannelType() != ChannelType.GUILD_PUBLIC_THREAD) { | ||
return false; | ||
} | ||
|
||
ThreadChannel thread = event.getThreadChannel(); | ||
String rootChannelName = thread.getParentChannel().getName(); | ||
return isStagingChannelName.test(rootChannelName) | ||
|| isOverviewChannelName.test(rootChannelName); | ||
} | ||
SquidXTV marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} |
10 changes: 10 additions & 0 deletions
10
application/src/main/java/org/togetherjava/tjbot/commands/filesharing/GistFile.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package org.togetherjava.tjbot.commands.filesharing; | ||
|
||
import org.jetbrains.annotations.NotNull; | ||
|
||
/** | ||
* @see <a href="https://docs.github.com/en/rest/gists/gists#create-a-gist">Create a Gist via | ||
* API</a> | ||
*/ | ||
record GistFile(@NotNull String content) { | ||
} |
13 changes: 13 additions & 0 deletions
13
application/src/main/java/org/togetherjava/tjbot/commands/filesharing/GistFiles.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package org.togetherjava.tjbot.commands.filesharing; | ||
|
||
import com.fasterxml.jackson.annotation.JsonAnyGetter; | ||
import org.jetbrains.annotations.NotNull; | ||
|
||
import java.util.Map; | ||
|
||
/** | ||
* @see <a href="https://docs.github.com/en/rest/gists/gists#create-a-gist">Create a Gist via | ||
* API</a> | ||
*/ | ||
record GistFiles(@NotNull @JsonAnyGetter Map<String, GistFile> nameToContent) { | ||
} |
12 changes: 12 additions & 0 deletions
12
application/src/main/java/org/togetherjava/tjbot/commands/filesharing/GistRequest.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package org.togetherjava.tjbot.commands.filesharing; | ||
|
||
import com.fasterxml.jackson.annotation.JsonProperty; | ||
import org.jetbrains.annotations.NotNull; | ||
|
||
/** | ||
* @see <a href="https://docs.github.com/en/rest/gists/gists#create-a-gist">Create a Gist via | ||
* API</a> | ||
*/ | ||
record GistRequest(@NotNull String description, @JsonProperty("public") boolean isPublic, | ||
@NotNull GistFiles files) { | ||
} |
23 changes: 23 additions & 0 deletions
23
application/src/main/java/org/togetherjava/tjbot/commands/filesharing/GistResponse.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
package org.togetherjava.tjbot.commands.filesharing; | ||
|
||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; | ||
import com.fasterxml.jackson.annotation.JsonProperty; | ||
import org.jetbrains.annotations.NotNull; | ||
|
||
/** | ||
* @see <a href="https://docs.github.com/en/rest/gists/gists#create-a-gist">Create a Gist via | ||
* API</a> | ||
*/ | ||
@JsonIgnoreProperties(ignoreUnknown = true) | ||
final class GistResponse { | ||
@JsonProperty("html_url") | ||
private String htmlUrl; | ||
|
||
public @NotNull String getHtmlUrl() { | ||
return this.htmlUrl; | ||
} | ||
|
||
public void setHtmlUrl(@NotNull String htmlUrl) { | ||
this.htmlUrl = htmlUrl; | ||
} | ||
Taz03 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} |
5 changes: 5 additions & 0 deletions
5
application/src/main/java/org/togetherjava/tjbot/commands/filesharing/package-info.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
/** | ||
* This package offers all the functionality for automatically uploading files to sharing services. | ||
* The core class is {@link org.togetherjava.tjbot.commands.filesharing.FileSharingMessageListener}. | ||
*/ | ||
package org.togetherjava.tjbot.commands.filesharing; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.