Skip to content

Commit 9c50dbd

Browse files
authored
used github api for creating gists (#796)
1 parent 330c4c7 commit 9c50dbd

File tree

6 files changed

+46
-196
lines changed

6 files changed

+46
-196
lines changed

application/build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ dependencies {
7373
implementation 'io.github.url-detector:url-detector:0.1.23'
7474

7575
implementation 'com.github.ben-manes.caffeine:caffeine:3.1.1'
76+
77+
implementation 'org.kohsuke:github-api:1.314'
7678

7779
testImplementation 'org.mockito:mockito-core:5.1.0'
7880
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.9.0'
Lines changed: 44 additions & 132 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,19 @@
11
package org.togetherjava.tjbot.features.filesharing;
22

3-
import com.fasterxml.jackson.core.JsonProcessingException;
4-
import com.fasterxml.jackson.databind.ObjectMapper;
53
import net.dv8tion.jda.api.entities.Member;
64
import net.dv8tion.jda.api.entities.Message;
75
import net.dv8tion.jda.api.entities.Role;
86
import net.dv8tion.jda.api.entities.User;
97
import net.dv8tion.jda.api.entities.channel.ChannelType;
108
import net.dv8tion.jda.api.entities.channel.concrete.ThreadChannel;
11-
import net.dv8tion.jda.api.entities.emoji.Emoji;
129
import net.dv8tion.jda.api.events.interaction.ModalInteractionEvent;
1310
import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent;
1411
import net.dv8tion.jda.api.events.interaction.component.SelectMenuInteractionEvent;
1512
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
16-
import net.dv8tion.jda.api.interactions.components.ActionRow;
1713
import net.dv8tion.jda.api.interactions.components.buttons.Button;
14+
import org.kohsuke.github.GHGist;
15+
import org.kohsuke.github.GHGistBuilder;
16+
import org.kohsuke.github.GitHubBuilder;
1817
import org.slf4j.Logger;
1918
import org.slf4j.LoggerFactory;
2019

@@ -28,18 +27,11 @@
2827
import java.io.IOException;
2928
import java.io.InputStream;
3029
import java.io.UncheckedIOException;
31-
import java.net.HttpURLConnection;
32-
import java.net.URI;
33-
import java.net.http.HttpClient;
34-
import java.net.http.HttpRequest;
35-
import java.net.http.HttpResponse;
3630
import java.nio.charset.StandardCharsets;
3731
import java.util.ArrayList;
3832
import java.util.List;
39-
import java.util.Map;
4033
import java.util.Set;
4134
import java.util.concurrent.CompletableFuture;
42-
import java.util.concurrent.ConcurrentHashMap;
4335
import java.util.function.Predicate;
4436
import java.util.regex.Pattern;
4537

@@ -48,17 +40,13 @@
4840
* contains a file with the given extension in the
4941
* {@link FileSharingMessageListener#extensionFilter}.
5042
*/
51-
public class FileSharingMessageListener extends MessageReceiverAdapter implements UserInteractor {
52-
53-
private static final Logger LOGGER = LoggerFactory.getLogger(FileSharingMessageListener.class);
54-
private static final ObjectMapper JSON = new ObjectMapper();
43+
public final class FileSharingMessageListener extends MessageReceiverAdapter
44+
implements UserInteractor {
45+
private static final Logger logger = LoggerFactory.getLogger(FileSharingMessageListener.class);
5546

5647
private final ComponentIdInteractor componentIdInteractor =
5748
new ComponentIdInteractor(getInteractionType(), getName());
5849

59-
private static final String SHARE_API = "https://api.github.com/gists";
60-
private static final HttpClient CLIENT = HttpClient.newHttpClient();
61-
6250
private final String gistApiKey;
6351
private final Set<String> extensionFilter = Set.of("txt", "java", "gradle", "xml", "kt", "json",
6452
"fxml", "css", "c", "h", "cpp", "py", "yml");
@@ -82,11 +70,8 @@ public FileSharingMessageListener(Config config) {
8270
@Override
8371
public void onMessageReceived(MessageReceivedEvent event) {
8472
User author = event.getAuthor();
85-
if (author.isBot() || event.isWebhookMessage()) {
86-
return;
87-
}
8873

89-
if (!isHelpThread(event)) {
74+
if (author.isBot() || event.isWebhookMessage() || !isHelpThread(event)) {
9075
return;
9176
}
9277

@@ -104,13 +89,36 @@ public void onMessageReceived(MessageReceivedEvent event) {
10489
try {
10590
processAttachments(event, attachments);
10691
} catch (Exception e) {
107-
LOGGER.error(
92+
logger.error(
10893
"Unknown error while processing attachments. Channel: {}, Author: {}, Message ID: {}.",
10994
event.getChannel().getName(), author.getId(), event.getMessageId(), e);
11095
}
11196
});
11297
}
11398

99+
@Override
100+
public void onButtonClick(ButtonInteractionEvent event, List<String> args) {
101+
Member interactionUser = event.getMember();
102+
String gistAuthorId = args.get(0);
103+
boolean hasSoftModPermissions =
104+
interactionUser.getRoles().stream().map(Role::getName).anyMatch(isSoftModRole);
105+
106+
if (!gistAuthorId.equals(interactionUser.getId()) && !hasSoftModPermissions) {
107+
event.reply("You do not have permission for this action.").setEphemeral(true).queue();
108+
return;
109+
}
110+
111+
String gistId = args.get(1);
112+
113+
try {
114+
new GitHubBuilder().withOAuthToken(gistApiKey).build().getGist(gistId).delete();
115+
116+
event.getMessage().delete().queue();
117+
} catch (IOException e) {
118+
logger.warn("Failed to delete gist with id {}", gistId, e);
119+
}
120+
}
121+
114122
private boolean isAttachmentRelevant(Message.Attachment attachment) {
115123
String extension = attachment.getFileExtension();
116124
if (extension == null) {
@@ -120,29 +128,28 @@ private boolean isAttachmentRelevant(Message.Attachment attachment) {
120128
}
121129

122130
private void processAttachments(MessageReceivedEvent event,
123-
List<Message.Attachment> attachments) {
124-
125-
Map<String, GistFile> nameToFile = new ConcurrentHashMap<>();
131+
List<Message.Attachment> attachments) throws IOException {
132+
GHGistBuilder gistBuilder = new GitHubBuilder().withOAuthToken(gistApiKey)
133+
.build()
134+
.createGist()
135+
.public_(false)
136+
.description("Uploaded by " + event.getAuthor().getAsTag());
126137

127138
List<CompletableFuture<Void>> tasks = new ArrayList<>();
139+
128140
for (Message.Attachment attachment : attachments) {
129141
CompletableFuture<Void> task = attachment.getProxy()
130142
.download()
131143
.thenApply(this::readAttachment)
132-
.thenAccept(
133-
content -> nameToFile.put(getNameOf(attachment), new GistFile(content)));
144+
.thenAccept(content -> gistBuilder.file(getNameOf(attachment), content));
134145

135146
tasks.add(task);
136147
}
137148

138149
tasks.forEach(CompletableFuture::join);
139150

140-
GistFiles files = new GistFiles(nameToFile);
141-
GistRequest request = new GistRequest(event.getAuthor().getName(), false, files);
142-
GistResponse response = uploadToGist(request);
143-
String url = response.getHtmlUrl();
144-
String gistId = response.getGistId();
145-
sendResponse(event, url, gistId);
151+
GHGist gist = gistBuilder.create();
152+
sendResponse(event, gist.getHtmlUrl().toString(), gist.getGistId());
146153
}
147154

148155
private String readAttachment(InputStream stream) {
@@ -173,62 +180,15 @@ private String getNameOf(Message.Attachment attachment) {
173180
return fileName;
174181
}
175182

176-
private GistResponse uploadToGist(GistRequest jsonRequest) {
177-
String body;
178-
try {
179-
body = JSON.writeValueAsString(jsonRequest);
180-
} catch (JsonProcessingException e) {
181-
throw new IllegalStateException(
182-
"Attempting to upload a file to gist, but unable to create the JSON request.",
183-
e);
184-
}
185-
186-
HttpRequest request = HttpRequest.newBuilder()
187-
.uri(URI.create(SHARE_API))
188-
.header("Accept", "application/json")
189-
.header("Authorization", "token " + gistApiKey)
190-
.POST(HttpRequest.BodyPublishers.ofString(body))
191-
.build();
192-
193-
HttpResponse<String> apiResponse;
194-
try {
195-
apiResponse = CLIENT.send(request, HttpResponse.BodyHandlers.ofString());
196-
} catch (IOException e) {
197-
throw new UncheckedIOException(e);
198-
} catch (InterruptedException e) {
199-
Thread.currentThread().interrupt();
200-
throw new IllegalStateException(
201-
"Attempting to upload a file to gist, but the request got interrupted.", e);
202-
}
203-
204-
int statusCode = apiResponse.statusCode();
205-
206-
if (statusCode < HttpURLConnection.HTTP_OK
207-
|| statusCode >= HttpURLConnection.HTTP_MULT_CHOICE) {
208-
throw new IllegalStateException("Gist API unexpected response: %s. Request JSON: %s"
209-
.formatted(apiResponse.body(), body));
210-
}
211-
212-
GistResponse gistResponse;
213-
try {
214-
gistResponse = JSON.readValue(apiResponse.body(), GistResponse.class);
215-
} catch (JsonProcessingException e) {
216-
throw new IllegalStateException(
217-
"Attempting to upload file to gist, but unable to parse its JSON response.", e);
218-
}
219-
return gistResponse;
220-
}
221-
222183
private void sendResponse(MessageReceivedEvent event, String url, String gistId) {
223184
Message message = event.getMessage();
224-
String messageContent =
225-
"I uploaded your attachments as **gist**. That way, they are easier to read for everyone, especially mobile users 👍";
185+
String messageContent = "I uploaded your attachments as **Gist**.";
226186

227-
Button gist = Button.link(url, "gist");
187+
Button gist = Button.link(url, "Gist");
228188

229189
Button delete = Button.danger(
230190
componentIdInteractor.generateComponentId(message.getAuthor().getId(), gistId),
231-
Emoji.fromUnicode("🗑️"));
191+
"Dismiss");
232192

233193
message.reply(messageContent).setActionRow(gist, delete).queue();
234194
}
@@ -243,32 +203,6 @@ private boolean isHelpThread(MessageReceivedEvent event) {
243203
return isHelpForumName.test(rootChannelName);
244204
}
245205

246-
private void deleteGist(String gistId) {
247-
HttpRequest request = HttpRequest.newBuilder()
248-
.uri(URI.create(SHARE_API + "/" + gistId))
249-
.header("Accept", "application/json")
250-
.header("Authorization", "token " + gistApiKey)
251-
.DELETE()
252-
.build();
253-
254-
HttpResponse<String> apiResponse;
255-
try {
256-
apiResponse = CLIENT.send(request, HttpResponse.BodyHandlers.ofString());
257-
} catch (IOException e) {
258-
throw new UncheckedIOException(e);
259-
} catch (InterruptedException e) {
260-
Thread.currentThread().interrupt();
261-
throw new IllegalStateException(
262-
"Attempting to delete a gist, but the request got interrupted.", e);
263-
}
264-
265-
int status = apiResponse.statusCode();
266-
if (status == 404) {
267-
String responseBody = apiResponse.body();
268-
LOGGER.warn("Gist API unexpected response while deleting gist: {}.", responseBody);
269-
}
270-
}
271-
272206
@Override
273207
public String getName() {
274208
return "filesharing";
@@ -284,27 +218,6 @@ public UserInteractionType getInteractionType() {
284218
return UserInteractionType.OTHER;
285219
}
286220

287-
@Override
288-
public void onButtonClick(ButtonInteractionEvent event, List<String> args) {
289-
Member interactionUser = event.getMember();
290-
String gistAuthorId = args.get(0);
291-
boolean hasSoftModPermissions =
292-
interactionUser.getRoles().stream().map(Role::getName).anyMatch(isSoftModRole);
293-
294-
if (!gistAuthorId.equals(interactionUser.getId()) && !hasSoftModPermissions) {
295-
event.reply("You do not have permission for this action.").setEphemeral(true).queue();
296-
return;
297-
}
298-
299-
Message message = event.getMessage();
300-
List<Button> buttons = message.getButtons();
301-
event.editComponents(ActionRow.of(buttons.stream().map(Button::asDisabled).toList()))
302-
.queue();
303-
304-
String gistId = args.get(1);
305-
deleteGist(gistId);
306-
}
307-
308221
@Override
309222
public void onSelectMenuSelection(SelectMenuInteractionEvent event, List<String> args) {
310223
throw new UnsupportedOperationException("Not used");
@@ -314,5 +227,4 @@ public void onSelectMenuSelection(SelectMenuInteractionEvent event, List<String>
314227
public void onModalSubmitted(ModalInteractionEvent event, List<String> args) {
315228
throw new UnsupportedOperationException("Not used");
316229
}
317-
318230
}

application/src/main/java/org/togetherjava/tjbot/features/filesharing/GistFile.java

Lines changed: 0 additions & 8 deletions
This file was deleted.

application/src/main/java/org/togetherjava/tjbot/features/filesharing/GistFiles.java

Lines changed: 0 additions & 12 deletions
This file was deleted.

application/src/main/java/org/togetherjava/tjbot/features/filesharing/GistRequest.java

Lines changed: 0 additions & 10 deletions
This file was deleted.

application/src/main/java/org/togetherjava/tjbot/features/filesharing/GistResponse.java

Lines changed: 0 additions & 34 deletions
This file was deleted.

0 commit comments

Comments
 (0)