Skip to content

Commit

Permalink
Add MediaChannels
Browse files Browse the repository at this point in the history
Common functionality between ForumChannel and MediaChannel was extracted
to a new ThreadOnlyChannel supertype.

See discord/discord-api-docs#6232
  • Loading branch information
lukellmann committed Aug 3, 2023
1 parent cbba5f3 commit 308f5e8
Show file tree
Hide file tree
Showing 26 changed files with 849 additions and 112 deletions.
5 changes: 5 additions & 0 deletions common/api/common.api
Original file line number Diff line number Diff line change
Expand Up @@ -1448,6 +1448,7 @@ public final class dev/kord/common/entity/ButtonStyle$Unknown : dev/kord/common/
}

public final class dev/kord/common/entity/ChannelFlag : java/lang/Enum {
public static final field HideMediaDownloadOptions Ldev/kord/common/entity/ChannelFlag;
public static final field Pinned Ldev/kord/common/entity/ChannelFlag;
public static final field RequireTag Ldev/kord/common/entity/ChannelFlag;
public final fun getCode ()I
Expand Down Expand Up @@ -1526,6 +1527,10 @@ public final class dev/kord/common/entity/ChannelType$GuildForum : dev/kord/comm
public static final field INSTANCE Ldev/kord/common/entity/ChannelType$GuildForum;
}

public final class dev/kord/common/entity/ChannelType$GuildMedia : dev/kord/common/entity/ChannelType {
public static final field INSTANCE Ldev/kord/common/entity/ChannelType$GuildMedia;
}

public final class dev/kord/common/entity/ChannelType$GuildNews : dev/kord/common/entity/ChannelType {
public static final field INSTANCE Ldev/kord/common/entity/ChannelType$GuildNews;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,11 @@ public sealed class ChannelType(
*/
public object GuildForum : ChannelType(15)

/**
* A channel that can only contain threads, similar to [GuildForum] channels.
*/
public object GuildMedia : ChannelType(16)

internal object Serializer : KSerializer<ChannelType> {
override val descriptor: SerialDescriptor =
PrimitiveSerialDescriptor("dev.kord.common.entity.ChannelType", PrimitiveKind.INT)
Expand All @@ -129,6 +134,7 @@ public sealed class ChannelType(
13 -> GuildStageVoice
14 -> GuildDirectory
15 -> GuildForum
16 -> GuildMedia
else -> Unknown(value)
}
}
Expand All @@ -151,6 +157,7 @@ public sealed class ChannelType(
GuildStageVoice,
GuildDirectory,
GuildForum,
GuildMedia,
)
}

Expand Down
14 changes: 11 additions & 3 deletions common/src/commonMain/kotlin/entity/DiscordChannel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@
"Student-Hubs-FAQ) containing the listed servers.",
),
Entry("GuildForum", intValue = 15, kDoc = "A channel that can only contain threads."),
Entry(
"GuildMedia", intValue = 16,
kDoc = "A channel that can only contain threads, similar to [GuildForum] channels.",
),
],
)

Expand Down Expand Up @@ -77,6 +81,7 @@
package dev.kord.common.entity

import dev.kord.common.entity.ChannelType.GuildForum
import dev.kord.common.entity.ChannelType.GuildMedia
import dev.kord.common.entity.optional.Optional
import dev.kord.common.entity.optional.OptionalBoolean
import dev.kord.common.entity.optional.OptionalInt
Expand Down Expand Up @@ -167,11 +172,14 @@ public data class DiscordChannel(

public enum class ChannelFlag(public val code: Int) {

/** This thread is pinned to the top of its parent [GuildForum] channel. */
/** This thread is pinned to the top of its parent [GuildForum] or [GuildMedia] channel. */
Pinned(1 shl 1),

/** Whether a tag is required to be specified when creating a thread in a [GuildForum] channel. */
RequireTag(1 shl 4);
/** Whether a tag is required to be specified when creating a thread in a [GuildForum] or a [GuildMedia] channel. */
RequireTag(1 shl 4),

/** When set hides the embedded media download options. Available only for [GuildMedia] channels. */
HideMediaDownloadOptions(1 shl 15);


public operator fun plus(flag: ChannelFlag): ChannelFlags = ChannelFlags(this.code or flag.code)
Expand Down
248 changes: 235 additions & 13 deletions core/api/core.api

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions core/src/commonMain/kotlin/Util.kt
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,7 @@ public fun Intents.IntentsBuilder.enableEvent(event: KClass<out Event>): Unit =
StageChannelCreateEvent::class,
TextChannelCreateEvent::class,
ForumChannelCreateEvent::class,
MediaChannelCreateEvent::class,
UnknownChannelCreateEvent::class,
VoiceChannelCreateEvent::class,

Expand All @@ -311,6 +312,7 @@ public fun Intents.IntentsBuilder.enableEvent(event: KClass<out Event>): Unit =
StageChannelUpdateEvent::class,
TextChannelUpdateEvent::class,
ForumChannelUpdateEvent::class,
MediaChannelUpdateEvent::class,
UnknownChannelUpdateEvent::class,
VoiceChannelUpdateEvent::class,

Expand All @@ -321,6 +323,7 @@ public fun Intents.IntentsBuilder.enableEvent(event: KClass<out Event>): Unit =
StageChannelDeleteEvent::class,
TextChannelDeleteEvent::class,
ForumChannelDeleteEvent::class,
MediaChannelDeleteEvent::class,
UnknownChannelDeleteEvent::class,
VoiceChannelDeleteEvent::class,

Expand Down
12 changes: 11 additions & 1 deletion core/src/commonMain/kotlin/behavior/GuildBehavior.kt
Original file line number Diff line number Diff line change
Expand Up @@ -777,13 +777,23 @@ public suspend inline fun GuildBehavior.createTextChannel(
public suspend inline fun GuildBehavior.createForumChannel(
name: String,
builder: ForumChannelCreateBuilder.() -> Unit = {}
): ForumChannel {
): ForumChannel {
contract { callsInPlace(builder, EXACTLY_ONCE) }
val response = kord.rest.guild.createForumChannel(id, name, builder)
val data = ChannelData.from(response)
return Channel.from(data, kord) as ForumChannel
}

public suspend inline fun GuildBehavior.createMediaChannel(
name: String,
builder: MediaChannelCreateBuilder.() -> Unit = {},
): MediaChannel {
contract { callsInPlace(builder, EXACTLY_ONCE) }
val response = kord.rest.guild.createMediaChannel(id, name, builder)
val data = ChannelData.from(response)
return Channel.from(data, kord) as MediaChannel
}

/**
* Requests to create a new voice channel.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -1,38 +1,18 @@
package dev.kord.core.behavior.channel

import dev.kord.common.exception.RequestException
import dev.kord.core.behavior.channel.threads.ThreadParentChannelBehavior
import dev.kord.core.cache.data.ChannelData
import dev.kord.core.entity.channel.Channel
import dev.kord.core.entity.channel.ForumChannel
import dev.kord.core.entity.channel.thread.TextChannelThread
import dev.kord.core.exception.EntityNotFoundException
import dev.kord.core.supplier.EntitySupplier
import dev.kord.core.supplier.EntitySupplyStrategy
import dev.kord.rest.builder.channel.ForumChannelModifyBuilder
import dev.kord.rest.builder.channel.thread.StartForumThreadBuilder
import dev.kord.rest.service.patchForumChannel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.datetime.Instant
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract

public interface ForumChannelBehavior : ThreadParentChannelBehavior {

override val activeThreads: Flow<TextChannelThread>
get() = super.activeThreads.filterIsInstance()

override fun getPublicArchivedThreads(before: Instant?, limit: Int?): Flow<TextChannelThread> {
return super.getPublicArchivedThreads(before, limit).filterIsInstance()
}

public suspend fun startPublicThread(
name: String,
builder: StartForumThreadBuilder.() -> Unit,
): TextChannelThread {
return unsafeStartForumThread(name, builder)
}
public interface ForumChannelBehavior : ThreadOnlyChannelBehavior {

/**
* Requests to get this behavior as a [ForumChannel].
Expand All @@ -52,15 +32,15 @@ public interface ForumChannelBehavior : ThreadParentChannelBehavior {
override suspend fun asChannelOrNull(): ForumChannel? = super.asChannelOrNull() as? ForumChannel

/**
* Retrieve the [ForumChannel] associated with this behaviour from the provided [EntitySupplier].
* Retrieve the [ForumChannel] associated with this behavior from the provided [EntitySupplier].
*
* @throws RequestException if anything went wrong during the request.
* @throws EntityNotFoundException if the user wasn't present.
*/
override suspend fun fetchChannel(): ForumChannel = super.fetchChannel() as ForumChannel

/**
* Retrieve the [ForumChannel] associated with this behaviour from the provided [EntitySupplier]
* Retrieve the [ForumChannel] associated with this behavior from the provided [EntitySupplier]
* returns null if the [ForumChannel] isn't present.
*
* @throws RequestException if anything went wrong during the request.
Expand All @@ -70,18 +50,6 @@ public interface ForumChannelBehavior : ThreadParentChannelBehavior {
override fun withStrategy(strategy: EntitySupplyStrategy<*>): ForumChannelBehavior
}

internal suspend fun ThreadParentChannelBehavior.unsafeStartForumThread(
name: String,
builder: StartForumThreadBuilder.() -> Unit,
): TextChannelThread {
contract { callsInPlace(builder, InvocationKind.EXACTLY_ONCE) }

val response = kord.rest.channel.startForumThread(id, name, builder)
val data = ChannelData.from(response)

return Channel.from(data, kord) as TextChannelThread
}

public suspend inline fun ForumChannelBehavior.edit(builder: ForumChannelModifyBuilder.() -> Unit): ForumChannel {
contract { callsInPlace(builder, InvocationKind.EXACTLY_ONCE) }

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package dev.kord.core.behavior.channel

import dev.kord.common.exception.RequestException
import dev.kord.core.cache.data.ChannelData
import dev.kord.core.entity.channel.Channel
import dev.kord.core.entity.channel.MediaChannel
import dev.kord.core.exception.EntityNotFoundException
import dev.kord.core.supplier.EntitySupplier
import dev.kord.core.supplier.EntitySupplyStrategy
import dev.kord.rest.builder.channel.MediaChannelModifyBuilder
import dev.kord.rest.service.patchMediaChannel
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract

public interface MediaChannelBehavior : ThreadOnlyChannelBehavior {

/**
* Requests to get this behavior as a [MediaChannel].
*
* @throws RequestException if anything went wrong during the request.
* @throws EntityNotFoundException if the channel wasn't present.
* @throws ClassCastException if the channel isn't a [MediaChannel].
*/
override suspend fun asChannel(): MediaChannel = super.asChannel() as MediaChannel

/**
* Requests to get this behavior as a [MediaChannel],
* returns null if the channel isn't present or if the channel isn't a [MediaChannel].
*
* @throws RequestException if anything went wrong during the request.
*/
override suspend fun asChannelOrNull(): MediaChannel? = super.asChannelOrNull() as? MediaChannel

/**
* Retrieve the [MediaChannel] associated with this behavior from the provided [EntitySupplier].
*
* @throws RequestException if anything went wrong during the request.
* @throws EntityNotFoundException if the user wasn't present.
*/
override suspend fun fetchChannel(): MediaChannel = super.fetchChannel() as MediaChannel

/**
* Retrieve the [MediaChannel] associated with this behavior from the provided [EntitySupplier]
* returns null if the [MediaChannel] isn't present.
*
* @throws RequestException if anything went wrong during the request.
*/
override suspend fun fetchChannelOrNull(): MediaChannel? = super.fetchChannelOrNull() as? MediaChannel

override fun withStrategy(strategy: EntitySupplyStrategy<*>): MediaChannelBehavior
}

public suspend inline fun MediaChannelBehavior.edit(builder: MediaChannelModifyBuilder.() -> Unit): MediaChannel {
contract { callsInPlace(builder, InvocationKind.EXACTLY_ONCE) }

val response = kord.rest.channel.patchMediaChannel(id, builder)
val data = ChannelData.from(response)

return Channel.from(data, kord) as MediaChannel
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package dev.kord.core.behavior.channel

import dev.kord.common.exception.RequestException
import dev.kord.core.behavior.channel.threads.ThreadParentChannelBehavior
import dev.kord.core.cache.data.ChannelData
import dev.kord.core.entity.channel.Channel
import dev.kord.core.entity.channel.ThreadOnlyChannel
import dev.kord.core.entity.channel.thread.TextChannelThread
import dev.kord.core.exception.EntityNotFoundException
import dev.kord.core.supplier.EntitySupplier
import dev.kord.core.supplier.EntitySupplyStrategy
import dev.kord.rest.builder.channel.thread.StartForumThreadBuilder
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.datetime.Instant
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract

public interface ThreadOnlyChannelBehavior : ThreadParentChannelBehavior {

override val activeThreads: Flow<TextChannelThread> get() = super.activeThreads.filterIsInstance()

override fun getPublicArchivedThreads(before: Instant?, limit: Int?): Flow<TextChannelThread> =
super.getPublicArchivedThreads(before, limit).filterIsInstance()

public suspend fun startPublicThread(name: String, builder: StartForumThreadBuilder.() -> Unit): TextChannelThread =
unsafeStartThreadInThreadOnlyChannel(name, builder)

/**
* Requests to get this behavior as a [ThreadOnlyChannel].
*
* @throws RequestException if anything went wrong during the request.
* @throws EntityNotFoundException if the channel wasn't present.
* @throws ClassCastException if the channel isn't a [ThreadOnlyChannel].
*/
override suspend fun asChannel(): ThreadOnlyChannel = super.asChannel() as ThreadOnlyChannel

/**
* Requests to get this behavior as a [ThreadOnlyChannel],
* returns null if the channel isn't present or if the channel isn't a [ThreadOnlyChannel].
*
* @throws RequestException if anything went wrong during the request.
*/
override suspend fun asChannelOrNull(): ThreadOnlyChannel? = super.asChannelOrNull() as? ThreadOnlyChannel

/**
* Retrieve the [ThreadOnlyChannel] associated with this behavior from the provided [EntitySupplier].
*
* @throws RequestException if anything went wrong during the request.
* @throws EntityNotFoundException if the user wasn't present.
*/
override suspend fun fetchChannel(): ThreadOnlyChannel = super.fetchChannel() as ThreadOnlyChannel

/**
* Retrieve the [ThreadOnlyChannel] associated with this behavior from the provided [EntitySupplier]
* returns null if the [ThreadOnlyChannel] isn't present.
*
* @throws RequestException if anything went wrong during the request.
*/
override suspend fun fetchChannelOrNull(): ThreadOnlyChannel? = super.fetchChannelOrNull() as? ThreadOnlyChannel

override fun withStrategy(strategy: EntitySupplyStrategy<*>): ThreadOnlyChannelBehavior
}

internal suspend fun ThreadParentChannelBehavior.unsafeStartThreadInThreadOnlyChannel(
name: String,
builder: StartForumThreadBuilder.() -> Unit,
): TextChannelThread {
contract { callsInPlace(builder, InvocationKind.EXACTLY_ONCE) }

val response = kord.rest.channel.startForumThread(id, name, builder)
val data = ChannelData.from(response)

return Channel.from(data, kord) as TextChannelThread
}
29 changes: 14 additions & 15 deletions core/src/commonMain/kotlin/entity/channel/Channel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -47,25 +47,24 @@ public interface Channel : ChannelBehavior {
kord: Kord,
strategy: EntitySupplyStrategy<*> = kord.resources.defaultStrategy
): Channel = when (data.type) {
GuildText -> TextChannel(data, kord)
DM, GroupDM -> DmChannel(data, kord)
GuildStageVoice -> StageChannel(data, kord)
GuildVoice -> VoiceChannel(data, kord)
GuildCategory -> Category(data, kord)
GuildNews -> NewsChannel(data, kord)
GuildForum -> ForumChannel(data, kord)
PublicNewsThread -> NewsChannelThread(data, kord)
PrivateThread, PublicGuildThread -> {
TextChannelThread(data, kord)
}
GuildText -> TextChannel(data, kord)
DM, GroupDM -> DmChannel(data, kord)
GuildStageVoice -> StageChannel(data, kord)
GuildVoice -> VoiceChannel(data, kord)
GuildCategory -> Category(data, kord)
GuildNews -> NewsChannel(data, kord)
GuildForum -> ForumChannel(data, kord)
GuildMedia -> MediaChannel(data, kord)
PublicNewsThread -> NewsChannelThread(data, kord)
PrivateThread, PublicGuildThread -> TextChannelThread(data, kord)

GuildDirectory, is Unknown -> {
if (data.threadMetadata.value == null) Channel(data, kord, strategy.supply(kord))
else ThreadChannel(data, kord, strategy.supply(kord))
}
GuildDirectory, is Unknown -> {
if (data.threadMetadata.value == null) Channel(data, kord, strategy.supply(kord))
else ThreadChannel(data, kord, strategy.supply(kord))
}
}
}
}


internal fun Channel(
Expand Down
Loading

0 comments on commit 308f5e8

Please sign in to comment.