Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement voice stage channel #239

Merged
merged 13 commits into from
Apr 26, 2021
3 changes: 3 additions & 0 deletions common/src/main/kotlin/entity/DiscordChannel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ sealed class ChannelType(val value: Int) {
/** A channel in which game developers can sell their game on Discord. */
object GuildStore : ChannelType(6)

object GuildStageVoice : ChannelType(13)

companion object;

internal object Serializer : KSerializer<ChannelType> {
Expand All @@ -108,6 +110,7 @@ sealed class ChannelType(val value: Int) {
4 -> GuildCategory
5 -> GuildNews
6 -> GuildStore
13 -> GuildStageVoice
else -> Unknown(code)
}

Expand Down
2 changes: 2 additions & 0 deletions common/src/main/kotlin/entity/DiscordGuild.kt
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,8 @@ data class DiscordVoiceState(
@SerialName("self_stream")
val selfStream: OptionalBoolean = OptionalBoolean.Missing,
val suppress: Boolean,
@SerialName("request_to_speak_timestamp")
val requestToSpeakTimestamp: String?
)

/**
Expand Down
7 changes: 5 additions & 2 deletions common/src/main/kotlin/entity/Permission.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package dev.kord.common.entity

import dev.kord.common.DiscordBitSet
import dev.kord.common.EmptyBitSet
import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializable
import kotlinx.serialization.descriptors.PrimitiveKind
Expand All @@ -18,7 +19,7 @@ class Permissions constructor(val code: DiscordBitSet) {
/**
* Returns this [Permissions] as a [Set] of [Permission]
*/
val values = Permission.values.filter { it.code in code }.toSet()
val values = Permission.values.filter { it.code in code }.toSet()

operator fun plus(permission: Permission): Permissions = Permissions(code + permission.code)

Expand Down Expand Up @@ -153,7 +154,8 @@ sealed class Permission(val code: DiscordBitSet) {
object ManageWebhooks : Permission(0x20000000)
object ManageEmojis : Permission(0x40000000)
object UseSlashCommands : Permission(0x80000000)
object All : Permission(0xFFFFFDFF)
object RequestToSpeak : Permission(0x100000000)
object All : Permission(Permission.values.fold(EmptyBitSet()) { acc, value -> acc + value.code })

companion object {
val values: Set<Permission>
Expand Down Expand Up @@ -189,6 +191,7 @@ sealed class Permission(val code: DiscordBitSet) {
ManageWebhooks,
ManageEmojis,
UseSlashCommands,
RequestToSpeak
)
}
}
26 changes: 26 additions & 0 deletions core/src/main/kotlin/behavior/channel/BaseVoiceChannelBehavior.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package dev.kord.core.behavior.channel

import dev.kord.cache.api.query
import dev.kord.common.exception.RequestException
import dev.kord.core.cache.data.VoiceStateData
import dev.kord.core.cache.idEq
import dev.kord.core.entity.VoiceState
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map

interface BaseVoiceChannelBehavior : GuildChannelBehavior {

/**
* Requests to retrieve the present voice states of this channel.
*
* This property is not resolvable through REST and will always use [KordCache] instead.
*
* The returned flow is lazily executed, any [RequestException] will be thrown on
* [terminal operators](https://kotlinlang.org/docs/reference/coroutines/flow.html#terminal-flow-operators) instead.
*/
val voiceStates: Flow<VoiceState>
get() = kord.cache.query<VoiceStateData> { idEq(VoiceStateData::channelId, id) }
.asFlow()
.map { VoiceState(it, kord) }

}
76 changes: 76 additions & 0 deletions core/src/main/kotlin/behavior/channel/StageChannelBehavior.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package dev.kord.core.behavior.channel

import dev.kord.common.entity.Snowflake
import dev.kord.core.Kord
import dev.kord.core.cache.data.ChannelData
import dev.kord.core.entity.channel.Channel
import dev.kord.core.entity.channel.StageChannel
import dev.kord.core.supplier.EntitySupplier
import dev.kord.core.supplier.EntitySupplyStrategy
import dev.kord.rest.builder.channel.StageVoiceChannelModifyBuilder
import dev.kord.rest.builder.guild.CurrentVoiceStateModifyBuilder
import dev.kord.rest.builder.guild.VoiceStateModifyBuilder
import dev.kord.rest.service.modifyCurrentVoiceState
import dev.kord.rest.service.modifyVoiceState
import dev.kord.rest.service.patchStageVoiceChannel
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract

interface StageChannelBehavior : BaseVoiceChannelBehavior {

override fun withStrategy(
strategy: EntitySupplyStrategy<*>
): StageChannelBehavior {
return StageChannelBehavior(id, guildId, kord, strategy.supply(kord))
}

fun StageChannelBehavior(
id: Snowflake,
guildId: Snowflake,
kord: Kord,
supplier: EntitySupplier = kord.defaultSupplier
): StageChannelBehavior = object : StageChannelBehavior {
override val guildId: Snowflake get() = guildId
override val id: Snowflake get() = id
override val kord get() = kord
override val supplier get() = supplier
}
HopeBaron marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is still here, while it should be defined outside the interface.

Suggested change
fun StageChannelBehavior(
id: Snowflake,
guildId: Snowflake,
kord: Kord,
supplier: EntitySupplier = kord.defaultSupplier
): StageChannelBehavior = object : StageChannelBehavior {
override val guildId: Snowflake get() = guildId
override val id: Snowflake get() = id
override val kord get() = kord
override val supplier get() = supplier
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm sorry didn't notice that I've copied the declaration even though you have mentioned it earlier

}

@OptIn(ExperimentalContracts::class)
suspend inline fun StageChannelBehavior.editCurrentVoiceState(builder: CurrentVoiceStateModifyBuilder.() -> Unit) {
contract { callsInPlace(builder, InvocationKind.EXACTLY_ONCE) }
kord.rest.guild.modifyCurrentVoiceState(guildId, id, builder)
}

@OptIn(ExperimentalContracts::class)
suspend inline fun StageChannelBehavior.editVoiceState(
userId: Snowflake,
builder: VoiceStateModifyBuilder.() -> Unit
) {
contract { callsInPlace(builder, InvocationKind.EXACTLY_ONCE) }
kord.rest.guild.modifyVoiceState(guildId, id, userId, builder)
}

@OptIn(ExperimentalContracts::class)
suspend fun StageChannelBehavior.edit(builder: StageVoiceChannelModifyBuilder.() -> Unit): StageChannel {
contract { callsInPlace(builder, InvocationKind.EXACTLY_ONCE) }
val response = kord.rest.channel.patchStageVoiceChannel(id, builder)

val data = ChannelData.from(response)
return Channel.from(data, kord) as StageChannel
}

fun StageChannelBehavior(
id: Snowflake,
guildId: Snowflake,
kord: Kord,
supplier: EntitySupplier = kord.defaultSupplier
): StageChannelBehavior = object : StageChannelBehavior {
override val guildId: Snowflake
get() = guildId
override val kord get() = kord
override val id: Snowflake get() = id
override val supplier get() = supplier
}
HopeBaron marked this conversation as resolved.
Show resolved Hide resolved
22 changes: 1 addition & 21 deletions core/src/main/kotlin/behavior/channel/VoiceChannelBehavior.kt
Original file line number Diff line number Diff line change
@@ -1,14 +1,9 @@
package dev.kord.core.behavior.channel

import dev.kord.cache.api.query

import dev.kord.common.entity.Snowflake
import dev.kord.common.exception.RequestException
import dev.kord.core.Kord
import dev.kord.core.cache.data.ChannelData
import dev.kord.core.cache.data.VoiceStateData
import dev.kord.core.cache.idEq
import dev.kord.core.entity.VoiceState
import dev.kord.core.entity.channel.Channel
import dev.kord.core.entity.channel.VoiceChannel
import dev.kord.core.exception.EntityNotFoundException
Expand All @@ -17,8 +12,6 @@ import dev.kord.core.supplier.EntitySupplyStrategy
import dev.kord.rest.builder.channel.VoiceChannelModifyBuilder
import dev.kord.rest.request.RestRequestException
import dev.kord.rest.service.patchVoiceChannel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import java.util.*
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind
Expand All @@ -27,20 +20,7 @@ import kotlin.contracts.contract
/**
* The behavior of a Discord Voice Channel associated to a guild.
*/
interface VoiceChannelBehavior : GuildChannelBehavior {

/**
* Requests to retrieve the present voice states of this channel.
*
* This property is not resolvable through REST and will always use [KordCache] instead.
*
* The returned flow is lazily executed, any [RequestException] will be thrown on
* [terminal operators](https://kotlinlang.org/docs/reference/coroutines/flow.html#terminal-flow-operators) instead.
*/
val voiceStates: Flow<VoiceState>
get() = kord.cache.query<VoiceStateData> { idEq(VoiceStateData::channelId, id) }
.asFlow()
.map { VoiceState(it, kord) }
interface VoiceChannelBehavior : BaseVoiceChannelBehavior {

/**
* Requests to get the this behavior as a [VoiceChannel].
Expand Down
8 changes: 4 additions & 4 deletions core/src/main/kotlin/cache/data/VoiceStateData.kt
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
package dev.kord.core.cache.data

import dev.kord.cache.api.data.description
import dev.kord.common.entity.DiscordGuildMember
import dev.kord.common.entity.DiscordVoiceState
import dev.kord.common.entity.Snowflake
import dev.kord.common.entity.optional.Optional
import dev.kord.common.entity.optional.OptionalBoolean
import dev.kord.common.entity.optional.OptionalSnowflake
import dev.kord.common.entity.optional.mapSnowflake
Expand All @@ -31,7 +29,8 @@ data class VoiceStateData(
val selfMute: Boolean,
val selfStream: OptionalBoolean = OptionalBoolean.Missing,
val suppress: Boolean,
) {
val requestToSpeakTimestamp: String?
) {
HopeBaron marked this conversation as resolved.
Show resolved Hide resolved

companion object {
val description = description(VoiceStateData::id)
Expand All @@ -48,7 +47,8 @@ data class VoiceStateData(
selfDeaf = selfDeaf,
selfMute = selfMute,
selfStream = selfStream,
suppress = suppress
suppress = suppress,
requestToSpeakTimestamp = requestToSpeakTimestamp
)
}
}
Expand Down
2 changes: 2 additions & 0 deletions core/src/main/kotlin/entity/VoiceState.kt
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ class VoiceState(

val isSuppressed: Boolean get() = data.suppress

val requestToSpeakTimestamp: String? get() = data.requestToSpeakTimestamp

/**
* Whether this user is streaming using "Go Live".
*/
Expand Down
1 change: 1 addition & 0 deletions core/src/main/kotlin/entity/channel/Channel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ interface Channel : ChannelBehavior {
): 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)
Expand Down
50 changes: 50 additions & 0 deletions core/src/main/kotlin/entity/channel/StageVoiceChannel.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package dev.kord.core.entity.channel

import dev.kord.common.entity.optional.getOrThrow
import dev.kord.core.Kord
import dev.kord.core.behavior.channel.ChannelBehavior
import dev.kord.core.behavior.channel.GuildChannelBehavior
import dev.kord.core.behavior.channel.StageChannelBehavior
import dev.kord.core.cache.data.ChannelData
import dev.kord.core.supplier.EntitySupplier
import dev.kord.core.supplier.EntitySupplyStrategy
import java.util.*

class StageChannel(
override val data: ChannelData,
override val kord: Kord,
override val supplier: EntitySupplier = kord.defaultSupplier
) : CategorizableChannel, StageChannelBehavior {

/**
* The bitrate (in bits) of this channel.
*/
val bitrate: Int get() = data.bitrate.getOrThrow()

/**
* The user limit of the voice channel.
*/
val userLimit: Int get() = data.userLimit.getOrThrow()

/**
* returns a new [StageChannel] with the given [strategy].
*
* @param strategy the strategy to use for the new instance. By default [EntitySupplyStrategy.CacheWithRestFallback].
*/
override fun withStrategy(strategy: EntitySupplyStrategy<*>): StageChannel =
StageChannel(data, kord, strategy.supply(kord))

override suspend fun asChannel(): StageChannel = this

override fun hashCode(): Int = Objects.hash(id, guildId)

override fun equals(other: Any?): Boolean = when (other) {
is GuildChannelBehavior -> other.id == id && other.guildId == guildId
is ChannelBehavior -> other.id == id
else -> false
}

override fun toString(): String {
return "VoiceChannel(data=$data, kord=$kord, supplier=$supplier)"
HopeBaron marked this conversation as resolved.
Show resolved Hide resolved
}
}
HopeBaron marked this conversation as resolved.
Show resolved Hide resolved
7 changes: 7 additions & 0 deletions core/src/main/kotlin/event/channel/ChannelCreateEvent.kt
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,13 @@ class VoiceChannelCreateEvent(override val channel: VoiceChannel, override val s
}
}


class StageChannelCreateEvent(override val channel: StageChannel, override val shard: Int) : ChannelCreateEvent {
override fun toString(): String {
return "StageChannelCreateEvent(channel=$channel, shard=$shard)"
}
}

class UnknownChannelCreateEvent(override val channel: Channel, override val shard: Int) : ChannelCreateEvent {
override fun toString(): String {
return "UnknownChannelCreateEvent(channel=$channel, shard=$shard)"
Expand Down
6 changes: 6 additions & 0 deletions core/src/main/kotlin/event/channel/ChannelDeleteEvent.kt
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@ class VoiceChannelDeleteEvent(override val channel: VoiceChannel, override val s
}
}

class StageChannelDeleteEvent(override val channel: StageChannel, override val shard: Int) : ChannelDeleteEvent {
override fun toString(): String {
return "StageChannelDeleteEvent(channel=$channel, shard=$shard)"
}
}


class UnknownChannelDeleteEvent(override val channel: Channel, override val shard: Int) : ChannelCreateEvent {
override fun toString(): String {
Expand Down
7 changes: 7 additions & 0 deletions core/src/main/kotlin/event/channel/ChannelUpdateEvent.kt
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,13 @@ class VoiceChannelUpdateEvent(override val channel: VoiceChannel, override val s
}


class StageChannelUpdateEvent(override val channel: StageChannel, override val shard: Int) : ChannelUpdateEvent {
override fun toString(): String {
return "StageChannelUpdateEvent(channel=$channel, shard=$shard)"
}
}


class UnknownChannelUpdateEvent(override val channel: Channel, override val shard: Int) : ChannelCreateEvent {
override fun toString(): String {
return "UnknownChannelUpdateEvent(channel=$channel, shard=$shard)"
Expand Down
3 changes: 3 additions & 0 deletions core/src/main/kotlin/gateway/handler/ChannelEventHandler.kt
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ internal class ChannelEventHandler(
is StoreChannel -> StoreChannelCreateEvent(channel, shard)
is DmChannel -> DMChannelCreateEvent(channel, shard)
is TextChannel -> TextChannelCreateEvent(channel, shard)
is StageChannel -> StageChannelCreateEvent(channel, shard)
is VoiceChannel -> VoiceChannelCreateEvent(channel, shard)
is Category -> CategoryCreateEvent(channel, shard)
else -> UnknownChannelCreateEvent(channel, shard)
Expand All @@ -60,6 +61,7 @@ internal class ChannelEventHandler(
is StoreChannel -> StoreChannelUpdateEvent(channel, shard)
is DmChannel -> DMChannelUpdateEvent(channel, shard)
is TextChannel -> TextChannelUpdateEvent(channel, shard)
is StageChannel -> StageChannelUpdateEvent(channel, shard)
is VoiceChannel -> VoiceChannelUpdateEvent(channel, shard)
is Category -> CategoryUpdateEvent(channel, shard)
else -> UnknownChannelUpdateEvent(channel, shard)
Expand All @@ -77,6 +79,7 @@ internal class ChannelEventHandler(
is StoreChannel -> StoreChannelDeleteEvent(channel, shard)
is DmChannel -> DMChannelDeleteEvent(channel, shard)
is TextChannel -> TextChannelDeleteEvent(channel, shard)
is StageChannel -> StageChannelDeleteEvent(channel, shard)
is VoiceChannel -> VoiceChannelDeleteEvent(channel, shard)
is Category -> CategoryDeleteEvent(channel, shard)
else -> UnknownChannelDeleteEvent(channel, shard)
Expand Down
Loading