diff --git a/common/src/main/kotlin/entity/DiscordChannel.kt b/common/src/main/kotlin/entity/DiscordChannel.kt index 3d96a0c5e8c..5fb9f3f522d 100644 --- a/common/src/main/kotlin/entity/DiscordChannel.kt +++ b/common/src/main/kotlin/entity/DiscordChannel.kt @@ -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 { @@ -108,6 +110,7 @@ sealed class ChannelType(val value: Int) { 4 -> GuildCategory 5 -> GuildNews 6 -> GuildStore + 13 -> GuildStageVoice else -> Unknown(code) } diff --git a/common/src/main/kotlin/entity/DiscordGuild.kt b/common/src/main/kotlin/entity/DiscordGuild.kt index 7893fdfba28..914c584b742 100644 --- a/common/src/main/kotlin/entity/DiscordGuild.kt +++ b/common/src/main/kotlin/entity/DiscordGuild.kt @@ -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? ) /** diff --git a/common/src/main/kotlin/entity/Permission.kt b/common/src/main/kotlin/entity/Permission.kt index c23a1051763..476413015e1 100644 --- a/common/src/main/kotlin/entity/Permission.kt +++ b/common/src/main/kotlin/entity/Permission.kt @@ -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 @@ -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) @@ -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 @@ -189,6 +191,7 @@ sealed class Permission(val code: DiscordBitSet) { ManageWebhooks, ManageEmojis, UseSlashCommands, + RequestToSpeak ) } } diff --git a/core/src/main/kotlin/behavior/channel/BaseVoiceChannelBehavior.kt b/core/src/main/kotlin/behavior/channel/BaseVoiceChannelBehavior.kt new file mode 100644 index 00000000000..4167f1ecc48 --- /dev/null +++ b/core/src/main/kotlin/behavior/channel/BaseVoiceChannelBehavior.kt @@ -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 + get() = kord.cache.query { idEq(VoiceStateData::channelId, id) } + .asFlow() + .map { VoiceState(it, kord) } + +} \ No newline at end of file diff --git a/core/src/main/kotlin/behavior/channel/StageChannelBehavior.kt b/core/src/main/kotlin/behavior/channel/StageChannelBehavior.kt new file mode 100644 index 00000000000..2d93dcc7bd7 --- /dev/null +++ b/core/src/main/kotlin/behavior/channel/StageChannelBehavior.kt @@ -0,0 +1,87 @@ +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.entity.channel.VoiceChannel +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.request.RestRequestException +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 { + + /** + * Returns a new [StageChannelBehavior] with the given [strategy]. + */ + override fun withStrategy( + strategy: EntitySupplyStrategy<*> + ): StageChannelBehavior { + return StageChannelBehavior(id, guildId, kord, strategy.supply(kord)) + } + +} + +/** + * Requests to edit the current user's voice state in this [StageChannel]. + */ +@OptIn(ExperimentalContracts::class) +suspend inline fun StageChannelBehavior.editCurrentVoiceState(builder: CurrentVoiceStateModifyBuilder.() -> Unit) { + contract { callsInPlace(builder, InvocationKind.EXACTLY_ONCE) } + kord.rest.guild.modifyCurrentVoiceState(guildId, id, builder) +} + +/** + * Requests to edit the another user's voice state in this [StageChannel]. + */ +@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) +} + +/** + * Requests to edit this channel. + * + * @return The edited [StageChannel]. + * + * @throws [RestRequestException] if something went wrong during the request. + */ +@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 + + override fun toString(): String { + return "StageChannelBehavior(id=$id, guildId=$guildId, kord=$kord, supplier=$supplier)" + } +} diff --git a/core/src/main/kotlin/behavior/channel/VoiceChannelBehavior.kt b/core/src/main/kotlin/behavior/channel/VoiceChannelBehavior.kt index 54c29c86d22..7a052f4cb95 100644 --- a/core/src/main/kotlin/behavior/channel/VoiceChannelBehavior.kt +++ b/core/src/main/kotlin/behavior/channel/VoiceChannelBehavior.kt @@ -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 @@ -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 @@ -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 - get() = kord.cache.query { idEq(VoiceStateData::channelId, id) } - .asFlow() - .map { VoiceState(it, kord) } +interface VoiceChannelBehavior : BaseVoiceChannelBehavior { /** * Requests to get the this behavior as a [VoiceChannel]. diff --git a/core/src/main/kotlin/cache/data/VoiceStateData.kt b/core/src/main/kotlin/cache/data/VoiceStateData.kt index 73d702d410f..5d580fa53ed 100644 --- a/core/src/main/kotlin/cache/data/VoiceStateData.kt +++ b/core/src/main/kotlin/cache/data/VoiceStateData.kt @@ -1,14 +1,11 @@ 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 -import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable val VoiceStateData.id get() = "$userId$guildId" @@ -31,6 +28,7 @@ data class VoiceStateData( val selfMute: Boolean, val selfStream: OptionalBoolean = OptionalBoolean.Missing, val suppress: Boolean, + val requestToSpeakTimestamp: String? ) { companion object { @@ -48,7 +46,8 @@ data class VoiceStateData( selfDeaf = selfDeaf, selfMute = selfMute, selfStream = selfStream, - suppress = suppress + suppress = suppress, + requestToSpeakTimestamp = requestToSpeakTimestamp ) } } diff --git a/core/src/main/kotlin/entity/VoiceState.kt b/core/src/main/kotlin/entity/VoiceState.kt index a8927beefdf..bd5009b5c32 100644 --- a/core/src/main/kotlin/entity/VoiceState.kt +++ b/core/src/main/kotlin/entity/VoiceState.kt @@ -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". */ diff --git a/core/src/main/kotlin/entity/channel/Channel.kt b/core/src/main/kotlin/entity/channel/Channel.kt index f2c9bc84a9b..b391501fa16 100644 --- a/core/src/main/kotlin/entity/channel/Channel.kt +++ b/core/src/main/kotlin/entity/channel/Channel.kt @@ -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) diff --git a/core/src/main/kotlin/entity/channel/StageVoiceChannel.kt b/core/src/main/kotlin/entity/channel/StageVoiceChannel.kt new file mode 100644 index 00000000000..e7f35962f6d --- /dev/null +++ b/core/src/main/kotlin/entity/channel/StageVoiceChannel.kt @@ -0,0 +1,54 @@ +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.* + +/** + * An instance of a [Discord Stage Channel](https://support.discord.com/hc/en-us/articles/1500005513722) + * associated to a community guild. + */ +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 "StageChannel(data=$data, kord=$kord, supplier=$supplier)" + } +} diff --git a/core/src/main/kotlin/event/channel/ChannelCreateEvent.kt b/core/src/main/kotlin/event/channel/ChannelCreateEvent.kt index 560f497dd71..c43203c0062 100644 --- a/core/src/main/kotlin/event/channel/ChannelCreateEvent.kt +++ b/core/src/main/kotlin/event/channel/ChannelCreateEvent.kt @@ -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)" diff --git a/core/src/main/kotlin/event/channel/ChannelDeleteEvent.kt b/core/src/main/kotlin/event/channel/ChannelDeleteEvent.kt index 5345f62b75a..08b8993721b 100644 --- a/core/src/main/kotlin/event/channel/ChannelDeleteEvent.kt +++ b/core/src/main/kotlin/event/channel/ChannelDeleteEvent.kt @@ -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 { diff --git a/core/src/main/kotlin/event/channel/ChannelUpdateEvent.kt b/core/src/main/kotlin/event/channel/ChannelUpdateEvent.kt index bb6db1e141f..a87316e07a4 100644 --- a/core/src/main/kotlin/event/channel/ChannelUpdateEvent.kt +++ b/core/src/main/kotlin/event/channel/ChannelUpdateEvent.kt @@ -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)" diff --git a/core/src/main/kotlin/gateway/handler/ChannelEventHandler.kt b/core/src/main/kotlin/gateway/handler/ChannelEventHandler.kt index eed6c8bb642..c2c23010f72 100644 --- a/core/src/main/kotlin/gateway/handler/ChannelEventHandler.kt +++ b/core/src/main/kotlin/gateway/handler/ChannelEventHandler.kt @@ -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) @@ -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) @@ -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) diff --git a/rest/src/main/kotlin/builder/channel/EditGuildChannelBuilder.kt b/rest/src/main/kotlin/builder/channel/EditGuildChannelBuilder.kt index be9c7c211b4..448a4c16b85 100644 --- a/rest/src/main/kotlin/builder/channel/EditGuildChannelBuilder.kt +++ b/rest/src/main/kotlin/builder/channel/EditGuildChannelBuilder.kt @@ -85,6 +85,36 @@ class VoiceChannelModifyBuilder : AuditRequestBuilder } + +@KordDsl +class StageVoiceChannelModifyBuilder : AuditRequestBuilder { + override var reason: String? = null + + private var _name: Optional = Optional.Missing() + var name: String? by ::_name.delegate() + + private var _position: OptionalInt? = OptionalInt.Missing + var position: Int? by ::_position.delegate() + + private var _topic: Optional = Optional.Missing() + var topic: String? by ::_topic.delegate() + + private var _parentId: OptionalSnowflake? = OptionalSnowflake.Missing + var parentId: Snowflake? by ::_parentId.delegate() + + private var _permissionOverwrites: Optional?> = Optional.Missing() + var permissionOverwrites: MutableSet? by ::_permissionOverwrites.delegate() + + override fun toRequest(): ChannelModifyPatchRequest = ChannelModifyPatchRequest( + name = _name, + position = _position, + parentId = _parentId, + topic = _topic, + permissionOverwrites = _permissionOverwrites + ) + +} + @KordDsl class NewsChannelModifyBuilder : AuditRequestBuilder { override var reason: String? = null diff --git a/rest/src/main/kotlin/builder/guild/VoiceStateModifyBuilder.kt b/rest/src/main/kotlin/builder/guild/VoiceStateModifyBuilder.kt new file mode 100644 index 00000000000..0b48f486ad6 --- /dev/null +++ b/rest/src/main/kotlin/builder/guild/VoiceStateModifyBuilder.kt @@ -0,0 +1,55 @@ +package dev.kord.rest.builder.guild + +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.delegate.delegate +import dev.kord.common.entity.optional.map +import dev.kord.rest.builder.RequestBuilder +import dev.kord.rest.json.request.CurrentVoiceStateModifyRequest +import dev.kord.rest.json.request.VoiceStateModifyRequest +import java.time.Instant + +class CurrentVoiceStateModifyBuilder(val channelId: Snowflake) : RequestBuilder { + + private var _requestToSpeakTimestamp: Optional = Optional.Missing() + + private var _suppress: OptionalBoolean = OptionalBoolean.Missing + + /** + * Sets the user's request to speak. + * The timestamp is used to sort how users appear on the moderators' request list. + * + * e.g: A client who requested to speak at 18:00, + * will appear above a client who requested to speak at 20:00 in the same timezone. + * + * * A date in the past is treated as "now" by Discord. + * * A null value removes the request to speak. + */ + var requestToSpeakTimestamp: Instant? by ::_requestToSpeakTimestamp.delegate() + + /** + * whether this user is muted by the current user. + */ + var suppress: Boolean? by ::_suppress.delegate() + + + override fun toRequest(): CurrentVoiceStateModifyRequest { + return CurrentVoiceStateModifyRequest(channelId, _suppress, _requestToSpeakTimestamp.map { it.toString() }) + } +} + + +class VoiceStateModifyBuilder(val channelId: Snowflake) : RequestBuilder { + + private var _suppress: OptionalBoolean = OptionalBoolean.Missing + + /** + * whether this user is muted by the current user. + */ + var suppress: Boolean? by ::_suppress.delegate() + + override fun toRequest(): VoiceStateModifyRequest { + return VoiceStateModifyRequest(channelId, _suppress) + } +} diff --git a/rest/src/main/kotlin/json/request/VoiceStateRequests.kt b/rest/src/main/kotlin/json/request/VoiceStateRequests.kt new file mode 100644 index 00000000000..2ac79942dba --- /dev/null +++ b/rest/src/main/kotlin/json/request/VoiceStateRequests.kt @@ -0,0 +1,24 @@ +package dev.kord.rest.json.request + +import dev.kord.common.entity.Snowflake +import dev.kord.common.entity.optional.Optional +import dev.kord.common.entity.optional.OptionalBoolean +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class CurrentVoiceStateModifyRequest( + @SerialName("channel_id") + val channelId: Snowflake, + val suppress: OptionalBoolean = OptionalBoolean.Missing, + @SerialName("request_to_speak_timestamp") + val requestToSpeakTimeStamp: Optional = Optional.Missing() +) + + +@Serializable +data class VoiceStateModifyRequest( + @SerialName("channel_id") + val channelId: Snowflake, + val suppress: OptionalBoolean = OptionalBoolean.Missing +) diff --git a/rest/src/main/kotlin/route/Route.kt b/rest/src/main/kotlin/route/Route.kt index 704953ff6f5..38fb000d9e3 100644 --- a/rest/src/main/kotlin/route/Route.kt +++ b/rest/src/main/kotlin/route/Route.kt @@ -639,6 +639,13 @@ sealed class Route( NoStrategy ) + object SelfVoiceStatePatch: + Route(HttpMethod.Patch, "/guilds/${GuildId}/voice-states/@me", NoStrategy) + + + object OthersVoiceStatePatch: + Route(HttpMethod.Patch, "/guilds/${GuildId}/voice-states/${UserId}", NoStrategy) + companion object { val baseUrl = "https://discord.com/api/$restVersion" } diff --git a/rest/src/main/kotlin/service/ChannelService.kt b/rest/src/main/kotlin/service/ChannelService.kt index 721e1a01957..e1e23f701e3 100644 --- a/rest/src/main/kotlin/service/ChannelService.kt +++ b/rest/src/main/kotlin/service/ChannelService.kt @@ -252,6 +252,18 @@ suspend inline fun ChannelService.patchVoiceChannel( return patchChannel(channelId, VoiceChannelModifyBuilder().apply(builder).toRequest()) } + +@OptIn(ExperimentalContracts::class) +suspend inline fun ChannelService.patchStageVoiceChannel( + channelId: Snowflake, + builder: StageVoiceChannelModifyBuilder.() -> Unit +): DiscordChannel { + contract { + callsInPlace(builder, InvocationKind.EXACTLY_ONCE) + } + return patchChannel(channelId, StageVoiceChannelModifyBuilder().apply(builder).toRequest()) +} + @OptIn(ExperimentalContracts::class) suspend inline fun ChannelService.patchStoreChannel( channelId: Snowflake, diff --git a/rest/src/main/kotlin/service/GuildService.kt b/rest/src/main/kotlin/service/GuildService.kt index 13a1cb2783b..ffafff1770f 100644 --- a/rest/src/main/kotlin/service/GuildService.kt +++ b/rest/src/main/kotlin/service/GuildService.kt @@ -5,10 +5,7 @@ import dev.kord.common.annotation.KordExperimental import dev.kord.common.entity.* import dev.kord.rest.builder.ban.BanCreateBuilder import dev.kord.rest.builder.channel.* -import dev.kord.rest.builder.guild.GuildCreateBuilder -import dev.kord.rest.builder.guild.GuildModifyBuilder -import dev.kord.rest.builder.guild.GuildWidgetModifyBuilder -import dev.kord.rest.builder.guild.WelcomeScreenModifyBuilder +import dev.kord.rest.builder.guild.* import dev.kord.rest.builder.integration.IntegrationModifyBuilder import dev.kord.rest.builder.member.MemberAddBuilder import dev.kord.rest.builder.member.MemberModifyBuilder @@ -387,6 +384,22 @@ class GuildService(requestHandler: RequestHandler) : RestService(requestHandler) body(GuildWelcomeScreenModifyRequest.serializer(), request) } + + suspend fun modifyCurrentVoiceState(guildId: Snowflake, request: CurrentVoiceStateModifyRequest) = + call(Route.SelfVoiceStatePatch) { + keys[Route.GuildId] = guildId + body(CurrentVoiceStateModifyRequest.serializer(), request) + } + + + suspend fun modifyVoiceState(guildId: Snowflake, userId: Snowflake, request: VoiceStateModifyRequest) = + call(Route.SelfVoiceStatePatch) { + keys[Route.GuildId] = guildId + keys[Route.UserId] = userId + body(VoiceStateModifyRequest.serializer(), request) + } + + } @OptIn(ExperimentalContracts::class) @@ -442,3 +455,28 @@ suspend inline fun GuildService.createCategory( val createBuilder = CategoryCreateBuilder(name).apply(builder) return createGuildChannel(guildId, createBuilder.toRequest(), createBuilder.reason) } + + +@OptIn(ExperimentalContracts::class) +suspend inline fun GuildService.modifyCurrentVoiceState( + guildId: Snowflake, + channelId: Snowflake, + builder: CurrentVoiceStateModifyBuilder.() -> Unit +) { + contract { callsInPlace(builder, InvocationKind.EXACTLY_ONCE) } + val modifyBuilder = CurrentVoiceStateModifyBuilder(channelId).apply(builder) + modifyCurrentVoiceState(guildId, modifyBuilder.toRequest()) +} + + +@OptIn(ExperimentalContracts::class) +suspend inline fun GuildService.modifyVoiceState( + guildId: Snowflake, + channelId: Snowflake, + userId: Snowflake, + builder: VoiceStateModifyBuilder.() -> Unit +) { + contract { callsInPlace(builder, InvocationKind.EXACTLY_ONCE) } + val modifyBuilder = VoiceStateModifyBuilder(channelId).apply(builder) + modifyVoiceState(guildId, userId, modifyBuilder.toRequest()) +}