diff --git a/BotCommands-jda-ktx/src/main/kotlin/dev/freya02/botcommands/jda/ktx/retrieve/Guild.kt b/BotCommands-jda-ktx/src/main/kotlin/dev/freya02/botcommands/jda/ktx/retrieve/Guild.kt index bef7b6e31..b77394071 100644 --- a/BotCommands-jda-ktx/src/main/kotlin/dev/freya02/botcommands/jda/ktx/retrieve/Guild.kt +++ b/BotCommands-jda-ktx/src/main/kotlin/dev/freya02/botcommands/jda/ktx/retrieve/Guild.kt @@ -106,13 +106,14 @@ suspend fun Guild.retrieveVanityInviteOrNull(): VanityInvite? { } /** - * Retrieves a thread by ID. + * Retrieves a thread belonging to this guild, by ID. * * The cached threads are checked first, and then a request is made. * * The [RestAction] may throw [InvalidChannelTypeException] if a channel with the ID was found, but isn't a thread. + * It may also throw [ParentGuildMismatchException] if the channel isn't from the same guild. * - * @see retrieveThreadChannelByIdOrNull + * @see Guild.retrieveThreadChannelByIdOrNull */ fun Guild.retrieveThreadChannelById(id: Long): CacheRestAction { return jda.deferredRestAction( @@ -124,6 +125,10 @@ fun Guild.retrieveThreadChannelById(id: Long): CacheRestAction { if (!channelType.isThread) throw InvalidChannelTypeException("Invalid channel type, expected a thread, got $channelType") + if (dataObject.getUnsignedLong("guild_id", 0) != this.idLong) { + throw ParentGuildMismatchException("Thread is not from the same guild") + } + (jda as JDAImpl).entityBuilder.createThreadChannel(this as GuildImpl, dataObject, this.idLong, false) } } @@ -131,29 +136,31 @@ fun Guild.retrieveThreadChannelById(id: Long): CacheRestAction { } /** - * Retrieves a thread by ID. + * Retrieves a thread belonging to this guild, by ID. * * The cached threads are checked first, and then a request is made. * * The [RestAction] may throw [InvalidChannelTypeException] if a channel with the ID was found, but isn't a thread. + * It may also throw [ParentGuildMismatchException] if the channel isn't from the same guild. * - * @see retrieveThreadChannelByIdOrNull + * @see Guild.retrieveThreadChannelByIdOrNull */ fun Guild.retrieveThreadChannelById(id: String): CacheRestAction { return retrieveThreadChannelById(MiscUtil.parseSnowflake(id)) } /** - * Retrieves a thread by ID. + * Retrieves a thread belonging to this guild, by ID. * * The cached threads are checked first, and then a request is made. * - * The returned thread may be null if: + * The returned thread may be `null` if: * - It doesn't exist * - The bot doesn't have access to it * - The channel isn't a thread + * - The channel isn't from the correct guild * - * @see retrieveThreadChannelById + * @see Guild.retrieveThreadChannelById */ suspend fun Guild.retrieveThreadChannelByIdOrNull(id: Long): ThreadChannel? { return runIgnoringResponseOrNull(ErrorResponse.UNKNOWN_CHANNEL, ErrorResponse.MISSING_ACCESS) { @@ -161,21 +168,24 @@ suspend fun Guild.retrieveThreadChannelByIdOrNull(id: Long): ThreadChannel? { retrieveThreadChannelById(id).await() } catch (_: InvalidChannelTypeException) { return null + } catch (_: ParentGuildMismatchException) { + return null } } } /** - * Retrieves a thread by ID. + * Retrieves a thread belonging to this guild, by ID. * * The cached threads are checked first, and then a request is made. * - * The returned thread may be null if: + * The returned thread may be `null` if: * - It doesn't exist * - The bot doesn't have access to it * - The channel isn't a thread + * - The channel isn't from the correct guild * - * @see retrieveThreadChannelById + * @see Guild.retrieveThreadChannelById */ suspend fun Guild.retrieveThreadChannelByIdOrNull(id: String): ThreadChannel? { return retrieveThreadChannelByIdOrNull(MiscUtil.parseSnowflake(id)) diff --git a/BotCommands-jda-ktx/src/main/kotlin/dev/freya02/botcommands/jda/ktx/retrieve/GuildNotFoundException.kt b/BotCommands-jda-ktx/src/main/kotlin/dev/freya02/botcommands/jda/ktx/retrieve/GuildNotFoundException.kt new file mode 100644 index 000000000..2b81730e2 --- /dev/null +++ b/BotCommands-jda-ktx/src/main/kotlin/dev/freya02/botcommands/jda/ktx/retrieve/GuildNotFoundException.kt @@ -0,0 +1,6 @@ +package dev.freya02.botcommands.jda.ktx.retrieve + +/** + * Exception thrown when a guild was expected but not found, typically in sharded applications. + */ +class GuildNotFoundException internal constructor(message: String) : IllegalStateException(message) diff --git a/BotCommands-jda-ktx/src/main/kotlin/dev/freya02/botcommands/jda/ktx/retrieve/InvalidChannelTypeException.kt b/BotCommands-jda-ktx/src/main/kotlin/dev/freya02/botcommands/jda/ktx/retrieve/InvalidChannelTypeException.kt index e0390f2a9..55a5dbb18 100644 --- a/BotCommands-jda-ktx/src/main/kotlin/dev/freya02/botcommands/jda/ktx/retrieve/InvalidChannelTypeException.kt +++ b/BotCommands-jda-ktx/src/main/kotlin/dev/freya02/botcommands/jda/ktx/retrieve/InvalidChannelTypeException.kt @@ -2,7 +2,5 @@ package dev.freya02.botcommands.jda.ktx.retrieve /** * Exception thrown when retrieving a channel by ID, but the type is incorrect. - * - * @see retrieveThreadChannelById */ -class InvalidChannelTypeException(message: String) : IllegalArgumentException(message) +class InvalidChannelTypeException internal constructor(message: String) : IllegalArgumentException(message) diff --git a/BotCommands-jda-ktx/src/main/kotlin/dev/freya02/botcommands/jda/ktx/retrieve/JDA.kt b/BotCommands-jda-ktx/src/main/kotlin/dev/freya02/botcommands/jda/ktx/retrieve/JDA.kt index 22dd707b3..bb974b081 100644 --- a/BotCommands-jda-ktx/src/main/kotlin/dev/freya02/botcommands/jda/ktx/retrieve/JDA.kt +++ b/BotCommands-jda-ktx/src/main/kotlin/dev/freya02/botcommands/jda/ktx/retrieve/JDA.kt @@ -2,14 +2,24 @@ package dev.freya02.botcommands.jda.ktx.retrieve import dev.freya02.botcommands.jda.ktx.IgnoreForMatch import dev.freya02.botcommands.jda.ktx.coroutines.await +import dev.freya02.botcommands.jda.ktx.deferredRestAction import dev.freya02.botcommands.jda.ktx.requests.runIgnoringResponseOrNull import net.dv8tion.jda.api.JDA import net.dv8tion.jda.api.entities.Entitlement import net.dv8tion.jda.api.entities.User import net.dv8tion.jda.api.entities.Webhook +import net.dv8tion.jda.api.entities.channel.ChannelType +import net.dv8tion.jda.api.entities.channel.concrete.ThreadChannel import net.dv8tion.jda.api.entities.sticker.StickerSnowflake import net.dv8tion.jda.api.entities.sticker.StickerUnion import net.dv8tion.jda.api.requests.ErrorResponse +import net.dv8tion.jda.api.requests.RestAction +import net.dv8tion.jda.api.requests.Route +import net.dv8tion.jda.api.requests.restaction.CacheRestAction +import net.dv8tion.jda.api.utils.MiscUtil +import net.dv8tion.jda.internal.JDAImpl +import net.dv8tion.jda.internal.entities.GuildImpl +import net.dv8tion.jda.internal.requests.RestActionImpl /** * Retrieves a [User] with the provided ID. @@ -107,3 +117,94 @@ suspend fun JDA.retrieveWebhookByIdOrNull(webhookId: Long): Webhook? { retrieveWebhookById(webhookId).await() } } + +/** + * Retrieves a thread from any guild, by ID. + * + * The cached threads are checked first, and then a request is made. + * + * The [RestAction] may throw [InvalidChannelTypeException] if a channel with the ID was found, but isn't a thread. + * If the thread is not owned by the current shard, [GuildNotFoundException] is thrown. + * + * @see JDA.retrieveThreadChannelByIdOrNull + */ +fun JDA.retrieveThreadChannelById(id: Long): CacheRestAction { + return deferredRestAction( + valueSupplier = { getThreadChannelById(id) }, + actionSupplier = { + RestActionImpl(this, Route.Channels.GET_CHANNEL.compile(id.toString())) { res, _ -> + val json = res.`object` + val channelType = json.getInt("type").let(ChannelType::fromId) + if (!channelType.isThread) + throw InvalidChannelTypeException("Invalid channel type, expected a thread, got $channelType") + + val guildId = json.getUnsignedLong("guild_id") + val guild = getGuildById(guildId) ?: run { + val expectedShard = (guildId shr 22) % shardInfo.shardTotal + val actualShard = shardInfo.shardId + if (expectedShard.toInt() == actualShard) { + throw GuildNotFoundException("Thread was found but its guild was not found") + } else { + throw GuildNotFoundException("Thread was found but its guild was not found as it is from a different shard") + } + } + + (this as JDAImpl).entityBuilder.createThreadChannel(guild as GuildImpl, json, guildId, false) + } + } + ) +} + +/** + * Retrieves a thread from any guild, by ID. + * + * The cached threads are checked first, and then a request is made. + * + * The [RestAction] may throw [InvalidChannelTypeException] if a channel with the ID was found, but isn't a thread. + * If the thread is not owned by the current shard, [GuildNotFoundException] is thrown. + * + * @see JDA.retrieveThreadChannelByIdOrNull + */ +fun JDA.retrieveThreadChannelById(id: String): CacheRestAction { + return retrieveThreadChannelById(MiscUtil.parseSnowflake(id)) +} + +/** + * Retrieves a thread from any guild, by ID. + * + * The cached threads are checked first, and then a request is made. + * + * The returned thread may be `null` if: + * - It doesn't exist + * - The bot doesn't have access to it + * - The channel isn't a thread + * - The thread isn't owned by a guild of this shard + * + * @see JDA.retrieveThreadChannelById + */ +suspend fun JDA.retrieveThreadChannelByIdOrNull(id: Long): ThreadChannel? { + return runIgnoringResponseOrNull(ErrorResponse.UNKNOWN_CHANNEL, ErrorResponse.MISSING_ACCESS) { + try { + retrieveThreadChannelById(id).await() + } catch (_: InvalidChannelTypeException) { + return null + } + } +} + +/** + * Retrieves a thread from any guild, by ID. + * + * The cached threads are checked first, and then a request is made. + * + * The returned thread may be `null` if: + * - It doesn't exist + * - The bot doesn't have access to it + * - The channel isn't a thread + * - The thread isn't owned by a guild of this shard + * + * @see JDA.retrieveThreadChannelById + */ +suspend fun JDA.retrieveThreadChannelByIdOrNull(id: String): ThreadChannel? { + return retrieveThreadChannelByIdOrNull(MiscUtil.parseSnowflake(id)) +} diff --git a/BotCommands-jda-ktx/src/main/kotlin/dev/freya02/botcommands/jda/ktx/retrieve/ParentGuildMismatchException.kt b/BotCommands-jda-ktx/src/main/kotlin/dev/freya02/botcommands/jda/ktx/retrieve/ParentGuildMismatchException.kt new file mode 100644 index 000000000..b6b409454 --- /dev/null +++ b/BotCommands-jda-ktx/src/main/kotlin/dev/freya02/botcommands/jda/ktx/retrieve/ParentGuildMismatchException.kt @@ -0,0 +1,6 @@ +package dev.freya02.botcommands.jda.ktx.retrieve + +/** + * Exception thrown when retrieving a channel by ID from a guild, but their expected guild is incorrect. + */ +class ParentGuildMismatchException internal constructor(message: String) : IllegalArgumentException(message)