diff --git a/src/main/java/com/alttd/AltitudeBot.java b/src/main/java/com/alttd/AltitudeBot.java index 7817fe0..b46811e 100644 --- a/src/main/java/com/alttd/AltitudeBot.java +++ b/src/main/java/com/alttd/AltitudeBot.java @@ -28,6 +28,7 @@ public class AltitudeBot { private static AltitudeBot instance; private static String path; + public static void main(String[] args) { if (args.length == 0) { //TODO change scripts so it works with this System.out.println("Please give the location for the configs as an arg"); @@ -49,7 +50,7 @@ public class AltitudeBot { initConfigs(); jda = JDABuilder.createDefault(SettingsConfig.TOKEN, GatewayIntent.GUILD_MEMBERS, - GatewayIntent.GUILD_BANS, + GatewayIntent.GUILD_MODERATION, GatewayIntent.GUILD_EMOJIS_AND_STICKERS, GatewayIntent.GUILD_WEBHOOKS, GatewayIntent.GUILD_PRESENCES, @@ -92,7 +93,7 @@ public class AltitudeBot { return file.getPath(); } catch (URISyntaxException e) { Logger.altitudeLogs.error("Unable to retrieve config directory"); - e.printStackTrace(); + Logger.altitudeLogs.error(e); } return (null); } diff --git a/src/main/java/com/alttd/config/SettingsConfig.java b/src/main/java/com/alttd/config/SettingsConfig.java index cc788e0..7736298 100644 --- a/src/main/java/com/alttd/config/SettingsConfig.java +++ b/src/main/java/com/alttd/config/SettingsConfig.java @@ -16,10 +16,12 @@ public class SettingsConfig extends AbstractConfig { // SETTINGS public static String TOKEN = "token"; + public static String KANBOARD_TOKEN = "kanboard-token"; public static boolean DEBUG = false; private void loadSettings() { TOKEN = settingsConfig.getString("settings.token", TOKEN); + KANBOARD_TOKEN = settingsConfig.getString("settings.kanboard-token", KANBOARD_TOKEN); DEBUG = settingsConfig.getBoolean("settings.debug", DEBUG); } diff --git a/src/main/java/com/alttd/contextMenuManager/ContextMenuManager.java b/src/main/java/com/alttd/contextMenuManager/ContextMenuManager.java index fa8c5e2..2d153ca 100644 --- a/src/main/java/com/alttd/contextMenuManager/ContextMenuManager.java +++ b/src/main/java/com/alttd/contextMenuManager/ContextMenuManager.java @@ -1,5 +1,6 @@ package com.alttd.contextMenuManager; +import com.alttd.contextMenuManager.contextMenus.ContextMenuForwardToKanboard; import com.alttd.contextMenuManager.contextMenus.ContextMenuRespondSuggestion; import com.alttd.modalManager.ModalManager; import com.alttd.util.Util; @@ -21,7 +22,8 @@ public class ContextMenuManager extends ListenerAdapter { public ContextMenuManager(ModalManager modalManager) { contextMenus = List.of( - new ContextMenuRespondSuggestion(modalManager) + new ContextMenuRespondSuggestion(modalManager), + new ContextMenuForwardToKanboard() ); } diff --git a/src/main/java/com/alttd/contextMenuManager/contextMenus/ContextMenuForwardToKanboard.java b/src/main/java/com/alttd/contextMenuManager/contextMenus/ContextMenuForwardToKanboard.java new file mode 100644 index 0000000..57d6e18 --- /dev/null +++ b/src/main/java/com/alttd/contextMenuManager/contextMenus/ContextMenuForwardToKanboard.java @@ -0,0 +1,52 @@ +package com.alttd.contextMenuManager.contextMenus; + +import com.alttd.contextMenuManager.DiscordContextMenu; +import com.alttd.util.Kanboard; +import com.alttd.util.Util; +import net.dv8tion.jda.api.Permission; +import net.dv8tion.jda.api.entities.Message; +import net.dv8tion.jda.api.events.interaction.command.MessageContextInteractionEvent; +import net.dv8tion.jda.api.events.interaction.command.UserContextInteractionEvent; +import net.dv8tion.jda.api.interactions.commands.DefaultMemberPermissions; +import net.dv8tion.jda.api.interactions.commands.build.CommandData; +import net.dv8tion.jda.api.interactions.commands.build.Commands; +import net.dv8tion.jda.api.requests.RestAction; + +import java.util.concurrent.CompletableFuture; + +public class ContextMenuForwardToKanboard extends DiscordContextMenu { + @Override + public String getContextMenuId() { + return "Forward To Kanboard"; + } + + @Override + public void execute(UserContextInteractionEvent event) { + event.getInteraction().replyEmbeds(Util.genericErrorEmbed("Error", "This interaction should have been a message interaction")) + .setEphemeral(true).queue(RestAction.getDefaultSuccess(), Util::handleFailure); + } + + @Override + public void execute(MessageContextInteractionEvent event) { + if (!event.isFromGuild()) { + return; + } + Message message = event.getInteraction().getTarget(); + + CompletableFuture booleanCompletableFuture = Kanboard.forwardMessageToKanboard(message); + event.deferReply(true).queue(defer -> booleanCompletableFuture.thenAcceptAsync(result -> { + if (!result) { + defer.editOriginalEmbeds(Util.genericErrorEmbed("Error", "Unable to forward message to Kanboard")).queue(); + return; + } + defer.editOriginalEmbeds(Util.genericSuccessEmbed("Success", "Forwarded message to Kanboard board!")).queue(); + })); + } + + @Override + public CommandData getUserContextInteraction() { + return Commands.message(getContextMenuId()) + .setGuildOnly(true) + .setDefaultPermissions(DefaultMemberPermissions.enabledFor(Permission.ADMINISTRATOR)); + } +} diff --git a/src/main/java/com/alttd/listeners/JDAListener.java b/src/main/java/com/alttd/listeners/JDAListener.java index 2b342db..b67e340 100644 --- a/src/main/java/com/alttd/listeners/JDAListener.java +++ b/src/main/java/com/alttd/listeners/JDAListener.java @@ -40,12 +40,13 @@ public class JDAListener extends ListenerAdapter { Logger.altitudeLogs.info("JDA ready to register commands."); LockedChannel lockedChannel = new LockedChannel(); ButtonManager buttonManager = new ButtonManager(); + TagAdded tagAdded = new TagAdded(); AppealRepost appealRepost = new AppealRepost(buttonManager); ModalManager modalManager = new ModalManager(buttonManager); ContextMenuManager contextMenuManager = new ContextMenuManager(modalManager); SelectMenuManager selectMenuManager = new SelectMenuManager(); CommandManager commandManager = new CommandManager(jda, modalManager, contextMenuManager, lockedChannel, selectMenuManager, buttonManager); - jda.addEventListener(buttonManager, modalManager, commandManager, contextMenuManager, lockedChannel, appealRepost, selectMenuManager); + jda.addEventListener(buttonManager, tagAdded, modalManager, commandManager, contextMenuManager, lockedChannel, appealRepost, selectMenuManager); PollQueries.loadPolls(buttonManager); new Timer().scheduleAtFixedRate(new PollTimerTask(jda, Logger.altitudeLogs), TimeUnit.MINUTES.toMillis(1), TimeUnit.MINUTES.toMillis(5)); startSchedulers(); diff --git a/src/main/java/com/alttd/listeners/TagAdded.java b/src/main/java/com/alttd/listeners/TagAdded.java new file mode 100644 index 0000000..3680d80 --- /dev/null +++ b/src/main/java/com/alttd/listeners/TagAdded.java @@ -0,0 +1,27 @@ +package com.alttd.listeners; + +import com.alttd.util.Kanboard; +import com.alttd.util.Logger; +import net.dv8tion.jda.api.entities.ISnowflake; +import net.dv8tion.jda.api.entities.channel.concrete.ThreadChannel; +import net.dv8tion.jda.api.entities.channel.forums.ForumTag; +import net.dv8tion.jda.api.events.channel.update.ChannelUpdateAppliedTagsEvent; +import net.dv8tion.jda.api.hooks.ListenerAdapter; + +import java.util.List; + +public class TagAdded extends ListenerAdapter { + + @Override + public void onChannelUpdateAppliedTags(ChannelUpdateAppliedTagsEvent event) { + List addedTags = event.getAddedTags(); + if (addedTags.stream().map(ISnowflake::getIdLong).noneMatch(id -> id == 1020378607052398632L)) {//TODO add tag id to config + return; + } + if (!(event.getChannel() instanceof ThreadChannel threadChannel)) { + return; + } + threadChannel.retrieveStartMessage().queue(Kanboard::forwardMessageToKanboard, error -> Logger.altitudeLogs.error(error)); + } + +} diff --git a/src/main/java/com/alttd/util/Kanboard.java b/src/main/java/com/alttd/util/Kanboard.java new file mode 100644 index 0000000..e711d7f --- /dev/null +++ b/src/main/java/com/alttd/util/Kanboard.java @@ -0,0 +1,130 @@ +package com.alttd.util; + +import com.alttd.config.SettingsConfig; +import net.dv8tion.jda.api.entities.Member; +import net.dv8tion.jda.api.entities.Message; +import net.dv8tion.jda.api.entities.channel.concrete.ThreadChannel; + +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Base64; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; + +public class Kanboard { + + public static CompletableFuture forwardMessageToKanboard(String title, String body, String webLink) { + HttpClient client = HttpClient.newHttpClient(); + HttpRequest request; + String jsonPayload = getPayload(UUID.randomUUID().toString(), title, body, webLink); + try { + byte[] kanboardTokenBytes = String.join(":", "jsonrpc", SettingsConfig.KANBOARD_TOKEN).getBytes(StandardCharsets.UTF_8); + String token = Base64.getEncoder().encodeToString(kanboardTokenBytes); + request = HttpRequest.newBuilder() + .uri(new URI("https://kanboard.alttd.com/jsonrpc.php")) + .header("Content-Type", "application/json") + .setHeader("Authorization", "Basic " + token) + .POST(HttpRequest.BodyPublishers.ofString(jsonPayload)) + .build(); + } catch (URISyntaxException e) { + Logger.altitudeLogs.error(e); + //TODO handle better + return CompletableFuture.completedFuture(false); + } + + return CompletableFuture.supplyAsync(() -> { + HttpResponse response; + try { + response = client.send(request, HttpResponse.BodyHandlers.ofString()); + } catch (InterruptedException | IOException e) { + Logger.altitudeLogs.error(e); + return false; + } + + if (response.statusCode() == 200 && response.body().matches(".*\"result\":[0-9]+,.*")) { + Logger.altitudeLogs.info(String.format("Response body: [%s]\nFor JsonPayload: [%s]", response.body(), jsonPayload)); + return true; + } else { + Logger.altitudeLogs.error(String.format("Invalid response: [%s]\nPayload: [%s]", response.body(), jsonPayload)); + return false; + } + }); + } + + public static CompletableFuture forwardMessageToKanboard(Message message) { + String body = message.getContentDisplay(); + if (messageIsSuggestion(message) && message.getGuildChannel() instanceof ThreadChannel threadChannel) { + return forwardSuggestionToKanboard(message, threadChannel); + } + String[] split = body.split("\n"); + String title; + if (split.length == 1) { + title = "No title"; + } else { + title = split[0]; + body = Arrays.stream(split).skip(1).filter(str -> !str.isBlank()).collect(Collectors.joining("\n")); + } + return forwardMessageToKanboard(title, body, getWebLink(message)); + } + + private static CompletableFuture forwardSuggestionToKanboard(Message message, ThreadChannel threadChannel) { + String title = threadChannel.getName(); + String content = Arrays.stream(message.getContentDisplay().split("\n")).skip(1).filter(str -> !str.isBlank()).collect(Collectors.joining("\n")); + return forwardMessageToKanboard(title, content, getWebLink(message)); + } + + private static boolean messageIsSuggestion(Message message) { + Member member = message.getMember(); + if (member == null) + return false; + + if (!member.getUser().equals(message.getJDA().getSelfUser())) { + return false; + } + if (!message.getContentRaw().startsWith("**Suggestion by:")) { + return false; + } + return true; + } + + private static String getPayload(String id, String title, String body, String webLink) { + return String.format(""" + { + "jsonrpc": "2.0", + "method": "createTask", + "id": "%s", + "params": { + "title": "%s", + "project_id": "3", + "description": "%s%s", + "column_id": "0", + "color_id": "red" + } + }""", + escapeSpecialJsonChars(id), + escapeSpecialJsonChars(title), + escapeSpecialJsonChars(body), + escapeSpecialJsonChars(String.format("\n\n[Discord link](%s)", webLink))); + } + + private static String getWebLink(Message message) { + return String.format("https://discord.com/channels/%d/%d/%d", message.getGuildIdLong(), message.getChannelIdLong(), message.getIdLong()); + } + + private static String escapeSpecialJsonChars(String s) { + return s.replace("\"", "\\\"") + .replace("\\", "\\\\") + .replace("\b", "\\b") + .replace("\f", "\\f") + .replace("\n", "\\n") + .replace("\r", "\\r") + .replace("\t", "\\t"); + } +}