diff --git a/api/src/main/java/com/discordsrv/api/discord/entity/channel/DiscordThreadContainer.java b/api/src/main/java/com/discordsrv/api/discord/entity/channel/DiscordThreadContainer.java index aafd5131..0c7c6157 100644 --- a/api/src/main/java/com/discordsrv/api/discord/entity/channel/DiscordThreadContainer.java +++ b/api/src/main/java/com/discordsrv/api/discord/entity/channel/DiscordThreadContainer.java @@ -23,6 +23,8 @@ package com.discordsrv.api.discord.entity.channel; +import com.discordsrv.api.DiscordSRVApi; +import net.dv8tion.jda.api.entities.channel.attribute.IThreadContainer; import org.jetbrains.annotations.NotNull; import java.util.List; @@ -42,4 +44,11 @@ public interface DiscordThreadContainer extends DiscordGuildChannel { CompletableFuture createThread(String name, boolean privateThread); CompletableFuture createThread(String name, long messageId); + + /** + * Returns the JDA representation of this object. This should not be used if it can be avoided. + * @return the JDA representation of this object + * @see DiscordSRVApi#jda() + */ + IThreadContainer getAsJDAThreadContainer(); } diff --git a/common/src/main/java/com/discordsrv/common/destination/DestinationLookupHelper.java b/common/src/main/java/com/discordsrv/common/destination/DestinationLookupHelper.java index 96150cf5..a9ab591e 100644 --- a/common/src/main/java/com/discordsrv/common/destination/DestinationLookupHelper.java +++ b/common/src/main/java/com/discordsrv/common/destination/DestinationLookupHelper.java @@ -7,6 +7,9 @@ import com.discordsrv.common.config.main.generic.DestinationConfig; import com.discordsrv.common.config.main.generic.ThreadConfig; import com.discordsrv.common.logging.Logger; import com.discordsrv.common.logging.NamedLogger; +import net.dv8tion.jda.api.Permission; +import net.dv8tion.jda.api.entities.channel.attribute.IThreadContainer; +import net.dv8tion.jda.api.entities.channel.concrete.ThreadChannel; import org.apache.commons.lang3.StringUtils; import java.util.*; @@ -91,7 +94,7 @@ public class DestinationLookupHelper { future = createThread(threadContainer, threadConfig, logFailures); } else if (existingThread != null) { // Unarchive existing thread - future = unarchiveThread(existingThread); + future = unarchiveThread(existingThread, logFailures); } else { // Lookup threads CompletableFuture> threads = @@ -103,7 +106,7 @@ public class DestinationLookupHelper { DiscordThreadChannel archivedThread = findThread(archivedThreads, threadConfig); if (archivedThread != null) { // Unarchive existing thread - return unarchiveThread(archivedThread); + return unarchiveThread(archivedThread, logFailures); } // Create thread @@ -157,14 +160,27 @@ public class DestinationLookupHelper { ThreadConfig threadConfig, boolean logFailures ) { + boolean forum = threadContainer instanceof DiscordForumChannel; + boolean privateThread = !forum && threadConfig.privateThread; + + IThreadContainer container = threadContainer.getAsJDAThreadContainer(); + if (!container.getGuild().getSelfMember().hasPermission(container, privateThread ? Permission.CREATE_PRIVATE_THREADS : Permission.CREATE_PUBLIC_THREADS)) { + if (logFailures) { + logger.error("Failed to create thread \"" + threadConfig.threadName + "\" " + + "in channel ID " + Long.toUnsignedString(threadContainer.getId()) + + ": lacking \"Create " + (privateThread ? "Private" : "Public") + " Threads\" permission"); + } + return CompletableFuture.completedFuture(null); + } + CompletableFuture future; - if (threadContainer instanceof DiscordForumChannel) { + if (forum) { future = ((DiscordForumChannel) threadContainer).createPost( threadConfig.threadName, SendableDiscordMessage.builder().setContent("\u200B").build() // zero-width-space ); } else { - future = threadContainer.createThread(threadConfig.threadName, threadConfig.privateThread); + future = threadContainer.createThread(threadConfig.threadName, privateThread); } return future.exceptionally(t -> { if (logFailures) { @@ -175,15 +191,27 @@ public class DestinationLookupHelper { }); } - private CompletableFuture unarchiveThread(DiscordThreadChannel channel) { + private CompletableFuture unarchiveThread(DiscordThreadChannel channel, boolean logFailures) { + ThreadChannel jdaChannel = channel.asJDA(); + if ((jdaChannel.isLocked() || !jdaChannel.isOwner()) && !jdaChannel.getGuild().getSelfMember().hasPermission(jdaChannel, Permission.MANAGE_THREADS)) { + if (logFailures) { + logger.error("Cannot unarchive thread \"" + channel.getName() + "\" " + + "in channel ID " + Long.toUnsignedString(channel.getParentChannel().getId()) + + ": lacking \"Manage Threads\" permission"); + } + return CompletableFuture.completedFuture(null); + } + return discordSRV.discordAPI().mapExceptions( channel.asJDA().getManager() .setArchived(false) .reason("DiscordSRV destination lookup") .submit() ).thenApply(v -> channel).exceptionally(t -> { - logger.error("Failed to unarchive thread \"" + channel.getName() + "\" " - + "in channel ID " + Long.toUnsignedString(channel.getParentChannel().getId()), t); + if (logFailures) { + logger.error("Failed to unarchive thread \"" + channel.getName() + "\" " + + "in channel ID " + Long.toUnsignedString(channel.getParentChannel().getId()), t); + } return null; }); } diff --git a/common/src/main/java/com/discordsrv/common/discord/api/entity/channel/AbstractDiscordThreadedGuildMessageChannel.java b/common/src/main/java/com/discordsrv/common/discord/api/entity/channel/AbstractDiscordThreadedGuildMessageChannel.java index c7a80b29..93b7594a 100644 --- a/common/src/main/java/com/discordsrv/common/discord/api/entity/channel/AbstractDiscordThreadedGuildMessageChannel.java +++ b/common/src/main/java/com/discordsrv/common/discord/api/entity/channel/AbstractDiscordThreadedGuildMessageChannel.java @@ -100,4 +100,8 @@ public abstract class AbstractDiscordThreadedGuildMessageChannel channel.createThreadChannel(name, messageId), result -> result); } + @Override + public IThreadContainer getAsJDAThreadContainer() { + return channel; + } + @Override public CompletableFuture createPost(String name, SendableDiscordMessage message) { return thread(