diff --git a/.gitignore b/.gitignore index 72e35328b6..62ff123564 100644 --- a/.gitignore +++ b/.gitignore @@ -162,6 +162,7 @@ logviewer/webpack.generated.js .DS_Store # VisualStudioCode +.vscode .vscode/* !.vscode/settings.json !.vscode/extensions.json diff --git a/application/build.gradle b/application/build.gradle index 9e9a68f325..652906f6df 100644 --- a/application/build.gradle +++ b/application/build.gradle @@ -27,7 +27,7 @@ jib { } } container { - mainClass = 'org.togetherjava.tjbot.BootstrapLauncher' + mainClass = 'org.togetherjava.tjbot.Application' setCreationTime(java.time.Instant.now().toString()) } } @@ -57,7 +57,7 @@ dependencies { implementation 'io.mikael:urlbuilder:2.0.9' - implementation 'org.jsoup:jsoup:1.15.3' + implementation 'org.jsoup:jsoup:1.16.1' implementation 'org.scilab.forge:jlatexmath:1.0.7' implementation 'org.scilab.forge:jlatexmath-font-greek:1.0.7' @@ -73,13 +73,18 @@ dependencies { implementation 'io.github.url-detector:url-detector:0.1.23' implementation 'com.github.ben-manes.caffeine:caffeine:3.1.1' + + implementation 'org.kohsuke:github-api:1.314' - testImplementation 'org.mockito:mockito-core:5.1.0' + testImplementation 'org.mockito:mockito-core:5.3.1' testImplementation 'org.junit.jupiter:junit-jupiter-api:5.9.0' testImplementation 'org.junit.jupiter:junit-jupiter-params:5.9.0' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.9.0' + + implementation "com.theokanning.openai-gpt3-java:api:$chatGPTVersion" + implementation "com.theokanning.openai-gpt3-java:service:$chatGPTVersion" } application { - mainClass = 'org.togetherjava.tjbot.BootstrapLauncher' + mainClass = 'org.togetherjava.tjbot.Application' } diff --git a/application/config.json.template b/application/config.json.template index da7081543f..84ac087cb0 100644 --- a/application/config.json.template +++ b/application/config.json.template @@ -88,4 +88,5 @@ ], "logInfoChannelWebhook": "", "logErrorChannelWebhook": "" + "openaiApiKey": "" } diff --git a/application/src/main/java/org/togetherjava/tjbot/BootstrapLauncher.java b/application/src/main/java/org/togetherjava/tjbot/BootstrapLauncher.java deleted file mode 100644 index 7a29f5895f..0000000000 --- a/application/src/main/java/org/togetherjava/tjbot/BootstrapLauncher.java +++ /dev/null @@ -1,43 +0,0 @@ -package org.togetherjava.tjbot; - -/** - * A bootstrap launcher with minimal dependencies that sets up needed parts and workarounds for the - * main logic to take over. - */ -public class BootstrapLauncher { - private BootstrapLauncher() { - throw new UnsupportedOperationException("Utility class, construction not supported"); - } - - /** - * Starts the main application. - * - * @param args arguments are forwarded, see {@link Application#main(String[])} - */ - public static void main(String[] args) { - setSystemProperties(); - - Application.main(args); - } - - /** - * Sets any system-properties before anything else is touched. - */ - @SuppressWarnings("squid:S106") // we can not afford any dependencies, even on a logger - private static void setSystemProperties() { - final int cores = Runtime.getRuntime().availableProcessors(); - if (cores <= 1) { - // If we are in a docker container, we officially might just have 1 core - // and Java would then set the parallelism of the common ForkJoinPool to 0. - // And 0 means no workers, so JDA cannot function, no Callback's on REST-Requests - // are executed - // NOTE This will likely be fixed with Java 18 or newer, remove afterwards (see - // https://bugs.openjdk.java.net/browse/JDK-8274349 and - // https://github.com/openjdk/jdk/pull/5784) - // noinspection UseOfSystemOutOrSystemErr - System.out.println("Available Cores \"" + cores + "\", setting Parallelism Flag"); - // noinspection AccessOfSystemProperties - System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism", "1"); - } - } -} diff --git a/application/src/main/java/org/togetherjava/tjbot/config/Config.java b/application/src/main/java/org/togetherjava/tjbot/config/Config.java index 830d4cadde..e5933ca262 100644 --- a/application/src/main/java/org/togetherjava/tjbot/config/Config.java +++ b/application/src/main/java/org/togetherjava/tjbot/config/Config.java @@ -36,6 +36,7 @@ public final class Config { private final String mediaOnlyChannelPattern; private final String logInfoChannelWebhook; private final String logErrorChannelWebhook; + private final String openaiApiKey; @SuppressWarnings("ConstructorWithTooManyParameters") @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) @@ -70,7 +71,8 @@ private Config(@JsonProperty(value = "token", required = true) String token, @JsonProperty(value = "logInfoChannelWebhook", required = true) String logInfoChannelWebhook, @JsonProperty(value = "logErrorChannelWebhook", - required = true) String logErrorChannelWebhook) { + required = true) String logErrorChannelWebhook, + @JsonProperty(value = "openaiApiKey", required = true) String openaiApiKey) { this.token = Objects.requireNonNull(token); this.gistApiKey = Objects.requireNonNull(gistApiKey); this.databasePath = Objects.requireNonNull(databasePath); @@ -93,6 +95,7 @@ private Config(@JsonProperty(value = "token", required = true) String token, this.blacklistedFileExtension = Objects.requireNonNull(blacklistedFileExtension); this.logInfoChannelWebhook = Objects.requireNonNull(logInfoChannelWebhook); this.logErrorChannelWebhook = Objects.requireNonNull(logErrorChannelWebhook); + this.openaiApiKey = Objects.requireNonNull(openaiApiKey); } /** @@ -304,4 +307,13 @@ public String getLogInfoChannelWebhook() { public String getLogErrorChannelWebhook() { return logErrorChannelWebhook; } + + /** + * The OpenAI token needed for communicating with OpenAI ChatGPT. + * + * @return the OpenAI API Token + */ + public String getOpenaiApiKey() { + return openaiApiKey; + } } diff --git a/application/src/main/java/org/togetherjava/tjbot/features/Features.java b/application/src/main/java/org/togetherjava/tjbot/features/Features.java index 0130fc5b18..7f1f3af046 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/Features.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/Features.java @@ -6,6 +6,8 @@ import org.togetherjava.tjbot.db.Database; import org.togetherjava.tjbot.features.basic.*; import org.togetherjava.tjbot.features.bookmarks.*; +import org.togetherjava.tjbot.features.chaptgpt.ChatGptCommand; +import org.togetherjava.tjbot.features.chaptgpt.ChatGptService; import org.togetherjava.tjbot.features.code.CodeMessageAutoDetection; import org.togetherjava.tjbot.features.code.CodeMessageHandler; import org.togetherjava.tjbot.features.code.CodeMessageManualDetection; @@ -72,6 +74,7 @@ public static Collection createFeatures(JDA jda, Database database, Con ScamHistoryStore scamHistoryStore = new ScamHistoryStore(database); HelpSystemHelper helpSystemHelper = new HelpSystemHelper(config, database); CodeMessageHandler codeMessageHandler = new CodeMessageHandler(); + ChatGptService chatGptService = new ChatGptService(config); // NOTE The system can add special system relevant commands also by itself, // hence this list may not necessarily represent the full list of all commands actually @@ -139,7 +142,7 @@ public static Collection createFeatures(JDA jda, Database database, Con features.add(new HelpThreadCommand(config, helpSystemHelper)); features.add(new ReportCommand(config)); features.add(new BookmarksCommand(bookmarksSystem)); - + features.add(new ChatGptCommand(chatGptService)); return features; } } diff --git a/application/src/main/java/org/togetherjava/tjbot/features/basic/SlashCommandEducator.java b/application/src/main/java/org/togetherjava/tjbot/features/basic/SlashCommandEducator.java index 9a6e77464a..f2c0d7e24b 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/basic/SlashCommandEducator.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/basic/SlashCommandEducator.java @@ -24,7 +24,7 @@ public final class SlashCommandEducator extends MessageReceiverAdapter { private static final Predicate IS_MESSAGE_COMMAND = Pattern.compile(""" [.!?] #Start of message command [a-zA-Z]{2,15} #Name of message command, e.g. 'close' - .* #Rest of the message + .*[^);] #Rest of the message (don't end with code stuff) """, Pattern.COMMENTS).asMatchPredicate(); @Override @@ -48,24 +48,25 @@ private void sendAdvice(Message message) { Try starting your message with a forward-slash `/` and Discord should open a popup showing you all available commands. A command might then look like `/foo` ๐Ÿ‘"""; - createReply(message, content, SLASH_COMMAND_POPUP_ADVICE_PATH).queue(); + createReply(message, content).queue(); } - private static MessageCreateAction createReply(Message messageToReplyTo, String content, - String imagePath) { + private static MessageCreateAction createReply(Message messageToReplyTo, String content) { boolean useImage = true; - InputStream imageData = HelpSystemHelper.class.getResourceAsStream("/" + imagePath); + InputStream imageData = + HelpSystemHelper.class.getResourceAsStream("/" + SLASH_COMMAND_POPUP_ADVICE_PATH); if (imageData == null) { useImage = false; } MessageEmbed embed = new EmbedBuilder().setDescription(content) - .setImage(useImage ? "attachment://" + imagePath : null) + .setImage(useImage ? "attachment://" + SLASH_COMMAND_POPUP_ADVICE_PATH : null) .build(); MessageCreateAction action = messageToReplyTo.replyEmbeds(embed); if (useImage) { - action = action.addFiles(FileUpload.fromData(imageData, imagePath)); + action = action + .addFiles(FileUpload.fromData(imageData, SLASH_COMMAND_POPUP_ADVICE_PATH)); } return action; diff --git a/application/src/main/java/org/togetherjava/tjbot/features/chaptgpt/ChatGptCommand.java b/application/src/main/java/org/togetherjava/tjbot/features/chaptgpt/ChatGptCommand.java new file mode 100644 index 0000000000..e53a34391d --- /dev/null +++ b/application/src/main/java/org/togetherjava/tjbot/features/chaptgpt/ChatGptCommand.java @@ -0,0 +1,86 @@ +package org.togetherjava.tjbot.features.chaptgpt; + +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; +import net.dv8tion.jda.api.events.interaction.ModalInteractionEvent; +import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; +import net.dv8tion.jda.api.interactions.components.Modal; +import net.dv8tion.jda.api.interactions.components.text.TextInput; +import net.dv8tion.jda.api.interactions.components.text.TextInputStyle; + +import org.togetherjava.tjbot.features.CommandVisibility; +import org.togetherjava.tjbot.features.SlashCommandAdapter; + +import java.time.Duration; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.List; +import java.util.Optional; + +/** + * The implemented command is {@code /chatgpt}, which allows users to ask ChatGPT a question, upon + * which it will respond with an AI generated answer. + */ +public final class ChatGptCommand extends SlashCommandAdapter { + private static final String QUESTION_INPUT = "question"; + private static final int MAX_MESSAGE_INPUT_LENGTH = 200; + private static final int MIN_MESSAGE_INPUT_LENGTH = 4; + private static final Duration COMMAND_COOLDOWN = Duration.of(10, ChronoUnit.SECONDS); + private final ChatGptService chatGptService; + + private final Cache userIdToAskedAtCache = + Caffeine.newBuilder().maximumSize(1_000).expireAfterWrite(COMMAND_COOLDOWN).build(); + + /** + * Creates an instance of the chatgpt command. + * + * @param chatGptService ChatGptService - Needed to make calls to ChatGPT API + */ + public ChatGptCommand(ChatGptService chatGptService) { + super("chatgpt", "Ask the ChatGPT AI a question!", CommandVisibility.GUILD); + + this.chatGptService = chatGptService; + } + + @Override + public void onSlashCommand(SlashCommandInteractionEvent event) { + Instant previousAskTime = userIdToAskedAtCache.getIfPresent(event.getMember().getId()); + if (previousAskTime != null) { + long timeRemainingUntilNextAsk = + COMMAND_COOLDOWN.minus(Duration.between(previousAskTime, Instant.now())) + .toSeconds(); + + event + .reply("Sorry, you need to wait another " + timeRemainingUntilNextAsk + + " second(s) before asking chatGPT another question.") + .setEphemeral(true) + .queue(); + return; + } + + TextInput body = TextInput + .create(QUESTION_INPUT, "Ask ChatGPT a question or get help with code", + TextInputStyle.PARAGRAPH) + .setPlaceholder("Put your question for ChatGPT here") + .setRequiredRange(MIN_MESSAGE_INPUT_LENGTH, MAX_MESSAGE_INPUT_LENGTH) + .build(); + + Modal modal = Modal.create(generateComponentId(), "ChatGPT").addActionRow(body).build(); + event.replyModal(modal).queue(); + } + + @Override + public void onModalSubmitted(ModalInteractionEvent event, List args) { + event.deferReply().queue(); + + Optional optional = + chatGptService.ask(event.getValue(QUESTION_INPUT).getAsString()); + if (optional.isPresent()) { + userIdToAskedAtCache.put(event.getMember().getId(), Instant.now()); + } + + String response = optional.orElse( + "An error has occurred while trying to communicate with ChatGPT. Please try again later"); + event.getHook().sendMessage(response).queue(); + } +} diff --git a/application/src/main/java/org/togetherjava/tjbot/features/chaptgpt/ChatGptService.java b/application/src/main/java/org/togetherjava/tjbot/features/chaptgpt/ChatGptService.java new file mode 100644 index 0000000000..08fbe7c134 --- /dev/null +++ b/application/src/main/java/org/togetherjava/tjbot/features/chaptgpt/ChatGptService.java @@ -0,0 +1,82 @@ +package org.togetherjava.tjbot.features.chaptgpt; + +import com.theokanning.openai.OpenAiHttpException; +import com.theokanning.openai.completion.chat.ChatCompletionRequest; +import com.theokanning.openai.completion.chat.ChatMessage; +import com.theokanning.openai.completion.chat.ChatMessageRole; +import com.theokanning.openai.service.OpenAiService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.togetherjava.tjbot.config.Config; + +import java.time.Duration; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +/** + * Service used to communicate to OpenAI API to generate responses. + */ +public class ChatGptService { + private static final Logger logger = LoggerFactory.getLogger(ChatGptService.class); + private static final Duration TIMEOUT = Duration.ofSeconds(10); + private static final int MAX_TOKENS = 3_000; + private boolean isDisabled = false; + private final OpenAiService openAiService; + + /** + * Creates instance of ChatGPTService + * + * @param config needed for token to OpenAI API. + */ + public ChatGptService(Config config) { + String apiKey = config.getOpenaiApiKey(); + if (apiKey.isBlank()) { + isDisabled = true; + } + + openAiService = new OpenAiService(apiKey, TIMEOUT); + } + + /** + * Prompt ChatGPT with a question and receive a response. + * + * @param question The question being asked of ChatGPT. Max is {@value MAX_TOKENS} tokens. + * @see ChatGPT + * Tokens. + * @return response from ChatGPT as a String. + */ + public Optional ask(String question) { + if (isDisabled) { + return Optional.empty(); + } + + try { + ChatMessage chatMessage = + new ChatMessage(ChatMessageRole.USER.value(), Objects.requireNonNull(question)); + ChatCompletionRequest chatCompletionRequest = ChatCompletionRequest.builder() + .model("gpt-3.5-turbo") + .messages(List.of(chatMessage)) + .frequencyPenalty(0.5) + .temperature(0.7) + .maxTokens(MAX_TOKENS) + .n(1) + .build(); + return Optional.ofNullable(openAiService.createChatCompletion(chatCompletionRequest) + .getChoices() + .get(0) + .getMessage() + .getContent()); + } catch (OpenAiHttpException openAiHttpException) { + logger.warn( + "There was an error using the OpenAI API: {} Code: {} Type: {} Status Code: {}", + openAiHttpException.getMessage(), openAiHttpException.code, + openAiHttpException.type, openAiHttpException.statusCode); + } catch (RuntimeException runtimeException) { + logger.warn("There was an error using the OpenAI API: {}", + runtimeException.getMessage()); + } + return Optional.empty(); + } +} diff --git a/application/src/main/java/org/togetherjava/tjbot/features/chaptgpt/package-info.java b/application/src/main/java/org/togetherjava/tjbot/features/chaptgpt/package-info.java new file mode 100644 index 0000000000..76c1d28638 --- /dev/null +++ b/application/src/main/java/org/togetherjava/tjbot/features/chaptgpt/package-info.java @@ -0,0 +1,10 @@ +/** + * This package contains the functionality to connect with ChatGPT via API calls. + */ +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault +package org.togetherjava.tjbot.features.chaptgpt; + +import org.togetherjava.tjbot.annotations.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/application/src/main/java/org/togetherjava/tjbot/features/filesharing/FileSharingMessageListener.java b/application/src/main/java/org/togetherjava/tjbot/features/filesharing/FileSharingMessageListener.java index 01ae063b97..3f511bda8f 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/filesharing/FileSharingMessageListener.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/filesharing/FileSharingMessageListener.java @@ -1,20 +1,19 @@ package org.togetherjava.tjbot.features.filesharing; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Message; import net.dv8tion.jda.api.entities.Role; import net.dv8tion.jda.api.entities.User; import net.dv8tion.jda.api.entities.channel.ChannelType; import net.dv8tion.jda.api.entities.channel.concrete.ThreadChannel; -import net.dv8tion.jda.api.entities.emoji.Emoji; import net.dv8tion.jda.api.events.interaction.ModalInteractionEvent; import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent; import net.dv8tion.jda.api.events.interaction.component.SelectMenuInteractionEvent; import net.dv8tion.jda.api.events.message.MessageReceivedEvent; -import net.dv8tion.jda.api.interactions.components.ActionRow; import net.dv8tion.jda.api.interactions.components.buttons.Button; +import org.kohsuke.github.GHGist; +import org.kohsuke.github.GHGistBuilder; +import org.kohsuke.github.GitHubBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -28,18 +27,11 @@ 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.ArrayList; import java.util.List; -import java.util.Map; import java.util.Set; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ConcurrentHashMap; import java.util.function.Predicate; import java.util.regex.Pattern; @@ -48,17 +40,13 @@ * contains a file with the given extension in the * {@link FileSharingMessageListener#extensionFilter}. */ -public class FileSharingMessageListener extends MessageReceiverAdapter implements UserInteractor { - - private static final Logger LOGGER = LoggerFactory.getLogger(FileSharingMessageListener.class); - private static final ObjectMapper JSON = new ObjectMapper(); +public final class FileSharingMessageListener extends MessageReceiverAdapter + implements UserInteractor { + private static final Logger logger = LoggerFactory.getLogger(FileSharingMessageListener.class); private final ComponentIdInteractor componentIdInteractor = new ComponentIdInteractor(getInteractionType(), getName()); - 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 extensionFilter = Set.of("txt", "java", "gradle", "xml", "kt", "json", "fxml", "css", "c", "h", "cpp", "py", "yml"); @@ -82,11 +70,8 @@ public FileSharingMessageListener(Config config) { @Override public void onMessageReceived(MessageReceivedEvent event) { User author = event.getAuthor(); - if (author.isBot() || event.isWebhookMessage()) { - return; - } - if (!isHelpThread(event)) { + if (author.isBot() || event.isWebhookMessage() || !isHelpThread(event)) { return; } @@ -104,13 +89,36 @@ public void onMessageReceived(MessageReceivedEvent event) { try { processAttachments(event, attachments); } catch (Exception e) { - LOGGER.error( + logger.error( "Unknown error while processing attachments. Channel: {}, Author: {}, Message ID: {}.", event.getChannel().getName(), author.getId(), event.getMessageId(), e); } }); } + @Override + public void onButtonClick(ButtonInteractionEvent event, List args) { + Member interactionUser = event.getMember(); + String gistAuthorId = args.get(0); + boolean hasSoftModPermissions = + interactionUser.getRoles().stream().map(Role::getName).anyMatch(isSoftModRole); + + if (!gistAuthorId.equals(interactionUser.getId()) && !hasSoftModPermissions) { + event.reply("You do not have permission for this action.").setEphemeral(true).queue(); + return; + } + + String gistId = args.get(1); + + try { + new GitHubBuilder().withOAuthToken(gistApiKey).build().getGist(gistId).delete(); + + event.getMessage().delete().queue(); + } catch (IOException e) { + logger.warn("Failed to delete gist with id {}", gistId, e); + } + } + private boolean isAttachmentRelevant(Message.Attachment attachment) { String extension = attachment.getFileExtension(); if (extension == null) { @@ -120,29 +128,28 @@ private boolean isAttachmentRelevant(Message.Attachment attachment) { } private void processAttachments(MessageReceivedEvent event, - List attachments) { - - Map nameToFile = new ConcurrentHashMap<>(); + List attachments) throws IOException { + GHGistBuilder gistBuilder = new GitHubBuilder().withOAuthToken(gistApiKey) + .build() + .createGist() + .public_(false) + .description("Uploaded by " + event.getAuthor().getAsTag()); List> tasks = new ArrayList<>(); + for (Message.Attachment attachment : attachments) { CompletableFuture task = attachment.getProxy() .download() .thenApply(this::readAttachment) - .thenAccept( - content -> nameToFile.put(getNameOf(attachment), new GistFile(content))); + .thenAccept(content -> gistBuilder.file(getNameOf(attachment), content)); tasks.add(task); } tasks.forEach(CompletableFuture::join); - GistFiles files = new GistFiles(nameToFile); - GistRequest request = new GistRequest(event.getAuthor().getName(), false, files); - GistResponse response = uploadToGist(request); - String url = response.getHtmlUrl(); - String gistId = response.getGistId(); - sendResponse(event, url, gistId); + GHGist gist = gistBuilder.create(); + sendResponse(event, gist.getHtmlUrl().toString(), gist.getGistId()); } private String readAttachment(InputStream stream) { @@ -173,62 +180,15 @@ private String getNameOf(Message.Attachment attachment) { return fileName; } - private GistResponse uploadToGist(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) - .POST(HttpRequest.BodyPublishers.ofString(body)) - .build(); - - HttpResponse apiResponse; - try { - apiResponse = CLIENT.send(request, HttpResponse.BodyHandlers.ofString()); - } 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: %s. Request JSON: %s" - .formatted(apiResponse.body(), 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; - } - private void sendResponse(MessageReceivedEvent event, String url, String gistId) { Message message = event.getMessage(); - String messageContent = - "I uploaded your attachments as **gist**. That way, they are easier to read for everyone, especially mobile users ๐Ÿ‘"; + String messageContent = "I uploaded your attachments as **Gist**."; - Button gist = Button.link(url, "gist"); + Button gist = Button.link(url, "Gist"); Button delete = Button.danger( componentIdInteractor.generateComponentId(message.getAuthor().getId(), gistId), - Emoji.fromUnicode("๐Ÿ—‘๏ธ")); + "Dismiss"); message.reply(messageContent).setActionRow(gist, delete).queue(); } @@ -243,32 +203,6 @@ private boolean isHelpThread(MessageReceivedEvent event) { return isHelpForumName.test(rootChannelName); } - private void deleteGist(String gistId) { - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(SHARE_API + "/" + gistId)) - .header("Accept", "application/json") - .header("Authorization", "token " + gistApiKey) - .DELETE() - .build(); - - HttpResponse apiResponse; - try { - apiResponse = CLIENT.send(request, HttpResponse.BodyHandlers.ofString()); - } catch (IOException e) { - throw new UncheckedIOException(e); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - throw new IllegalStateException( - "Attempting to delete a gist, but the request got interrupted.", e); - } - - int status = apiResponse.statusCode(); - if (status == 404) { - String responseBody = apiResponse.body(); - LOGGER.warn("Gist API unexpected response while deleting gist: {}.", responseBody); - } - } - @Override public String getName() { return "filesharing"; @@ -284,27 +218,6 @@ public UserInteractionType getInteractionType() { return UserInteractionType.OTHER; } - @Override - public void onButtonClick(ButtonInteractionEvent event, List args) { - Member interactionUser = event.getMember(); - String gistAuthorId = args.get(0); - boolean hasSoftModPermissions = - interactionUser.getRoles().stream().map(Role::getName).anyMatch(isSoftModRole); - - if (!gistAuthorId.equals(interactionUser.getId()) && !hasSoftModPermissions) { - event.reply("You do not have permission for this action.").setEphemeral(true).queue(); - return; - } - - Message message = event.getMessage(); - List