Add support for sending to forums

This commit is contained in:
Vankka 2024-06-20 23:03:58 +03:00
parent f5b8829de1
commit f0427d4890
No known key found for this signature in database
GPG Key ID: 62E48025ED4E7EBB
7 changed files with 57 additions and 13 deletions

View File

@ -1,7 +1,12 @@
package com.discordsrv.api.discord.entity.channel; package com.discordsrv.api.discord.entity.channel;
import com.discordsrv.api.discord.entity.JDAEntity; import com.discordsrv.api.discord.entity.JDAEntity;
import com.discordsrv.api.discord.entity.message.SendableDiscordMessage;
import net.dv8tion.jda.api.entities.channel.concrete.ForumChannel; import net.dv8tion.jda.api.entities.channel.concrete.ForumChannel;
import java.util.concurrent.CompletableFuture;
public interface DiscordForumChannel extends DiscordChannel, DiscordThreadContainer, JDAEntity<ForumChannel> { public interface DiscordForumChannel extends DiscordChannel, DiscordThreadContainer, JDAEntity<ForumChannel> {
CompletableFuture<DiscordThreadChannel> createPost(String name, SendableDiscordMessage message);
} }

View File

@ -26,7 +26,10 @@ public class ThreadConfig {
@Comment("Specify the text or forum channel id and the name of the thread (the thread will be automatically created if it doesn't exist)") @Comment("Specify the text or forum channel id and the name of the thread (the thread will be automatically created if it doesn't exist)")
public Long channelId = 0L; public Long channelId = 0L;
public String threadName = "Minecraft Server chat bridge"; public String threadName = "Minecraft Server chat bridge";
@Comment("Does not effect forums")
public boolean privateThread = false; public boolean privateThread = false;
} }

View File

@ -28,6 +28,7 @@ import com.discordsrv.api.discord.entity.guild.DiscordGuild;
import com.discordsrv.api.discord.entity.guild.DiscordRole; import com.discordsrv.api.discord.entity.guild.DiscordRole;
import com.discordsrv.api.discord.entity.interaction.command.CommandType; import com.discordsrv.api.discord.entity.interaction.command.CommandType;
import com.discordsrv.api.discord.entity.interaction.command.DiscordCommand; import com.discordsrv.api.discord.entity.interaction.command.DiscordCommand;
import com.discordsrv.api.discord.entity.message.SendableDiscordMessage;
import com.discordsrv.api.discord.exception.NotReadyException; import com.discordsrv.api.discord.exception.NotReadyException;
import com.discordsrv.api.discord.exception.RestErrorResponseException; import com.discordsrv.api.discord.exception.RestErrorResponseException;
import com.discordsrv.common.DiscordSRV; import com.discordsrv.common.DiscordSRV;
@ -49,7 +50,9 @@ import com.github.benmanes.caffeine.cache.Expiry;
import net.dv8tion.jda.api.JDA; import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.entities.*; import net.dv8tion.jda.api.entities.*;
import net.dv8tion.jda.api.entities.channel.Channel; import net.dv8tion.jda.api.entities.channel.Channel;
import net.dv8tion.jda.api.entities.channel.attribute.IWebhookContainer;
import net.dv8tion.jda.api.entities.channel.concrete.*; import net.dv8tion.jda.api.entities.channel.concrete.*;
import net.dv8tion.jda.api.entities.channel.middleman.GuildChannel;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.entities.emoji.CustomEmoji; import net.dv8tion.jda.api.entities.emoji.CustomEmoji;
import net.dv8tion.jda.api.exceptions.ErrorResponseException; import net.dv8tion.jda.api.exceptions.ErrorResponseException;
@ -168,7 +171,7 @@ public class DiscordAPIImpl implements DiscordAPI {
"Failed to deliver message to thread \"" "Failed to deliver message to thread \""
+ threadConfig.threadName + "\" in channel " + container + threadConfig.threadName + "\" in channel " + container
).accept(t); ).accept(t);
throw new RuntimeException(); // Just here to fail the future throw new RuntimeException("Failed to deliver message to one or more threads");
} }
if (threadChannel != null) { if (threadChannel != null) {
@ -248,7 +251,7 @@ public class DiscordAPIImpl implements DiscordAPI {
private void unarchiveOrCreateThread( private void unarchiveOrCreateThread(
ThreadConfig config, ThreadConfig config,
DiscordThreadContainer container, DiscordThreadContainer container,
DiscordThreadChannel thread, @Nullable DiscordThreadChannel thread,
CompletableFuture<DiscordThreadChannel> future CompletableFuture<DiscordThreadChannel> future
) { ) {
if (thread != null) { if (thread != null) {
@ -268,7 +271,16 @@ public class DiscordAPIImpl implements DiscordAPI {
return; return;
} }
container.createThread(config.threadName, config.privateThread).whenComplete(((threadChannel, t) -> { CompletableFuture<DiscordThreadChannel> createFuture;
if (container instanceof DiscordForumChannel) {
createFuture = ((DiscordForumChannel) container).createPost(
config.threadName,
SendableDiscordMessage.builder().setContent("\u200B").build() // zero-width-space
);
} else {
createFuture = container.createThread(config.threadName, config.privateThread);
}
createFuture.whenComplete(((threadChannel, t) -> {
if (t != null) { if (t != null) {
future.completeExceptionally(t); future.completeExceptionally(t);
} else { } else {
@ -538,12 +550,13 @@ public class DiscordAPIImpl implements DiscordAPI {
return notReady(); return notReady();
} }
TextChannel textChannel = jda.getTextChannelById(channelId); GuildChannel channel = jda.getGuildChannelById(channelId);
if (textChannel == null) { IWebhookContainer webhookContainer = channel instanceof IWebhookContainer ? (IWebhookContainer) channel : null;
if (webhookContainer == null) {
return CompletableFutureUtil.failed(new IllegalArgumentException("Channel could not be found")); return CompletableFutureUtil.failed(new IllegalArgumentException("Channel could not be found"));
} }
return textChannel.retrieveWebhooks().submit().thenApply(webhooks -> { return webhookContainer.retrieveWebhooks().submit().thenApply(webhooks -> {
Webhook hook = null; Webhook hook = null;
for (Webhook webhook : webhooks) { for (Webhook webhook : webhooks) {
User user = webhook.getOwnerAsUser(); User user = webhook.getOwnerAsUser();
@ -563,7 +576,7 @@ public class DiscordAPIImpl implements DiscordAPI {
return CompletableFuture.completedFuture(webhook); return CompletableFuture.completedFuture(webhook);
} }
return textChannel.createWebhook("DSRV").submit(); return webhookContainer.createWebhook("DSRV").submit();
}).thenApply(webhook -> }).thenApply(webhook ->
WebhookClient.createClient( WebhookClient.createClient(
webhook.getJDA(), webhook.getJDA(),

View File

@ -4,10 +4,14 @@ import com.discordsrv.api.discord.entity.channel.DiscordChannelType;
import com.discordsrv.api.discord.entity.channel.DiscordForumChannel; import com.discordsrv.api.discord.entity.channel.DiscordForumChannel;
import com.discordsrv.api.discord.entity.channel.DiscordThreadChannel; import com.discordsrv.api.discord.entity.channel.DiscordThreadChannel;
import com.discordsrv.api.discord.entity.guild.DiscordGuild; import com.discordsrv.api.discord.entity.guild.DiscordGuild;
import com.discordsrv.api.discord.entity.message.SendableDiscordMessage;
import com.discordsrv.common.DiscordSRV; import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.discord.api.entity.message.util.SendableDiscordMessageUtil;
import net.dv8tion.jda.api.entities.channel.attribute.IThreadContainer; import net.dv8tion.jda.api.entities.channel.attribute.IThreadContainer;
import net.dv8tion.jda.api.entities.channel.concrete.ForumChannel; import net.dv8tion.jda.api.entities.channel.concrete.ForumChannel;
import net.dv8tion.jda.api.entities.channel.concrete.ThreadChannel; import net.dv8tion.jda.api.entities.channel.concrete.ThreadChannel;
import net.dv8tion.jda.api.entities.channel.forums.ForumPost;
import net.dv8tion.jda.api.requests.restaction.AbstractThreadCreateAction;
import net.dv8tion.jda.api.requests.restaction.ThreadChannelAction; import net.dv8tion.jda.api.requests.restaction.ThreadChannelAction;
import net.dv8tion.jda.api.requests.restaction.pagination.ThreadChannelPaginationAction; import net.dv8tion.jda.api.requests.restaction.pagination.ThreadChannelPaginationAction;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@ -95,20 +99,31 @@ public class DiscordForumChannelImpl implements DiscordForumChannel {
@Override @Override
public CompletableFuture<DiscordThreadChannel> createThread(String name, boolean privateThread) { public CompletableFuture<DiscordThreadChannel> createThread(String name, boolean privateThread) {
return thread(channel -> channel.createThreadChannel(name, privateThread)); throw new IllegalStateException("Cannot create Threads in Forums without a message");
} }
@Override @Override
public CompletableFuture<DiscordThreadChannel> createThread(String name, long messageId) { public CompletableFuture<DiscordThreadChannel> createThread(String name, long messageId) {
return thread(channel -> channel.createThreadChannel(name, messageId)); return thread(channel -> channel.createThreadChannel(name, messageId), result -> result);
}
@Override
public CompletableFuture<DiscordThreadChannel> createPost(String name, SendableDiscordMessage message) {
return thread(
channel -> channel.createForumPost(name, SendableDiscordMessageUtil.toJDASend(message)),
ForumPost::getThreadChannel
);
} }
@SuppressWarnings("CodeBlock2Expr") @SuppressWarnings("CodeBlock2Expr")
private CompletableFuture<DiscordThreadChannel> thread(Function<ForumChannel, ThreadChannelAction> action) { private <R> CompletableFuture<DiscordThreadChannel> thread(
Function<ForumChannel, AbstractThreadCreateAction<R, ?>> action,
Function<R, ThreadChannel> resultMapper
) {
return discordSRV.discordAPI().mapExceptions(() -> { return discordSRV.discordAPI().mapExceptions(() -> {
return action.apply(channel) return action.apply(channel)
.submit() .submit()
.thenApply(channel -> discordSRV.discordAPI().getThreadChannel(channel)); .thenApply(result -> discordSRV.discordAPI().getThreadChannel(resultMapper.apply(result)));
}); });
} }

View File

@ -9,6 +9,7 @@ public class FullBootExtension implements BeforeAllCallback, ExtensionContext.St
public static String BOT_TOKEN = System.getenv("DISCORDSRV_AUTOTEST_BOT_TOKEN"); public static String BOT_TOKEN = System.getenv("DISCORDSRV_AUTOTEST_BOT_TOKEN");
public static String TEST_CHANNEL_ID = System.getenv("DISCORDSRV_AUTOTEST_CHANNEL_ID"); public static String TEST_CHANNEL_ID = System.getenv("DISCORDSRV_AUTOTEST_CHANNEL_ID");
public static String FORUM_CHANNEL_ID = System.getenv("DISCORDSRV_AUTOTEST_FORUM_ID");
public boolean started = false; public boolean started = false;
@ -16,6 +17,7 @@ public class FullBootExtension implements BeforeAllCallback, ExtensionContext.St
public void beforeAll(ExtensionContext context) { public void beforeAll(ExtensionContext context) {
Assumptions.assumeTrue(BOT_TOKEN != null, "Automated testing bot token"); Assumptions.assumeTrue(BOT_TOKEN != null, "Automated testing bot token");
Assumptions.assumeTrue(TEST_CHANNEL_ID != null, "Automated testing channel id"); Assumptions.assumeTrue(TEST_CHANNEL_ID != null, "Automated testing channel id");
Assumptions.assumeTrue(FORUM_CHANNEL_ID != null, "Automated testing forum id");
if (started) return; if (started) return;
started = true; started = true;

View File

@ -215,6 +215,7 @@ public class MockDiscordSRV extends AbstractDiscordSRV<IBootstrap, MainConfig, C
DestinationConfig destination = global.destination = new DestinationConfig(); DestinationConfig destination = global.destination = new DestinationConfig();
long channelId = Long.parseLong(FullBootExtension.TEST_CHANNEL_ID); long channelId = Long.parseLong(FullBootExtension.TEST_CHANNEL_ID);
long forumId = Long.parseLong(FullBootExtension.FORUM_CHANNEL_ID);
List<Long> channelIds = destination.channelIds; List<Long> channelIds = destination.channelIds;
channelIds.clear(); channelIds.clear();
@ -222,9 +223,14 @@ public class MockDiscordSRV extends AbstractDiscordSRV<IBootstrap, MainConfig, C
List<ThreadConfig> threadConfigs = destination.threads; List<ThreadConfig> threadConfigs = destination.threads;
threadConfigs.clear(); threadConfigs.clear();
ThreadConfig thread = new ThreadConfig(); ThreadConfig thread = new ThreadConfig();
thread.channelId = channelId; thread.channelId = channelId;
threadConfigs.add(thread); threadConfigs.add(thread);
ThreadConfig forumThread = new ThreadConfig();
thread.channelId = forumId;
threadConfigs.add(forumThread);
} }
return config; return config;
@ -239,7 +245,7 @@ public class MockDiscordSRV extends AbstractDiscordSRV<IBootstrap, MainConfig, C
} }
@Override @Override
public void load() throws ConfigException { public void load() {
messagesConfigLoaded = true; messagesConfigLoaded = true;
} }
}; };

View File

@ -146,7 +146,7 @@ public class MinecraftToDiscordChatMessageTest {
} }
} }
success.complete(text == 1 && thread == 1); success.complete(text == 1 && thread == 2);
} }
} }
} }