Skip to content

Commit

Permalink
Implement Stage instances (kordlib#291)
Browse files Browse the repository at this point in the history
* Add low-level implementation of stage instances

* Add helper functions

* Add core entities and api representations

* Expose creation of StageInstanceBehavior to unsafe
- Revert outdated change

* Final additions
- Add StageInstanceBehavior.asStageInstance
- Fix compiler issue
- Add StageChannelBehavior.getStageInstance()

* Add StageInstances to EntitySupplier.kt

* Add StageInstances to EntitySupplier.kt

* Fix typo

* Apply requested changes
  • Loading branch information
DRSchlaubi committed Jun 7, 2021
1 parent 42c7de9 commit 4b0d10c
Show file tree
Hide file tree
Showing 16 changed files with 213 additions and 3 deletions.
23 changes: 23 additions & 0 deletions common/src/main/kotlin/entity/DiscordStageInstance.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package dev.kord.common.entity

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable


/**
* A _Stage Instance_ holds information about a live stage.
*
* @property id The id of this Stage instance
* @property guildId The guild id of the associated Stage channel
* @property channelId The id of the associated Stage channel
* @property topic The topic of the Stage instance (1-120 characters)
*/
@Serializable
data class DiscordStageInstance(
val id: Snowflake,
@SerialName("guild_id")
val guildId: Snowflake,
@SerialName("channel_id")
val channelId: Snowflake,
val topic: String
)
5 changes: 4 additions & 1 deletion core/src/main/kotlin/Unsafe.kt
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ class Unsafe(private val kord: Kord) {
fun webhook(id: Snowflake): WebhookBehavior =
WebhookBehavior(id, kord)

fun stageInstance(id: Snowflake, channelId: Snowflake): StageInstanceBehavior = StageInstanceBehavior(
id, channelId, kord, kord.defaultSupplier
)

override fun toString(): String {
return "Unsafe"
}
Expand All @@ -87,5 +91,4 @@ class Unsafe(private val kord: Kord) {
): GlobalApplicationCommandBehavior =
GlobalApplicationCommandBehavior(applicationId, commandId, service)


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

import dev.kord.common.entity.Snowflake
import dev.kord.core.Kord
import dev.kord.core.cache.data.StageInstanceData
import dev.kord.core.entity.KordEntity
import dev.kord.core.entity.StageInstance
import dev.kord.core.entity.Strategizable
import dev.kord.core.supplier.EntitySupplier
import dev.kord.core.supplier.EntitySupplyStrategy
import dev.kord.rest.json.request.StageInstanceUpdateRequest

interface StageInstanceBehavior : KordEntity, Strategizable {
val channelId: Snowflake

suspend fun delete(): Unit = kord.rest.stageInstance.deleteStageInstance(channelId)

suspend fun update(topic: String): StageInstance {
val instance = kord.rest.stageInstance.updateStageInstance(channelId, StageInstanceUpdateRequest(topic))
val data = StageInstanceData.from(instance)

return StageInstance(data, kord, supplier)
}

suspend fun asStageInstance(): StageInstance = supplier.getStageInstance(channelId)

override fun withStrategy(strategy: EntitySupplyStrategy<*>): StageInstanceBehavior =
StageInstanceBehavior(id, channelId, kord, strategy.supply(kord))
}

internal fun StageInstanceBehavior(id: Snowflake, channelId: Snowflake, kord: Kord, supplier: EntitySupplier) =
object : StageInstanceBehavior {
override val channelId: Snowflake
get() = channelId
override val kord: Kord
get() = kord
override val id: Snowflake
get() = id
override val supplier: EntitySupplier
get() = supplier

override fun toString(): String {
return "StageInstanceBehavior(id=$id, channelId=$id, kord=$kord, supplier=$supplier)"
}
}
16 changes: 15 additions & 1 deletion core/src/main/kotlin/behavior/channel/StageChannelBehavior.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,17 @@ 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.cache.data.StageInstanceData
import dev.kord.core.entity.StageInstance
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.createStageInstance
import dev.kord.rest.service.modifyCurrentVoiceState
import dev.kord.rest.service.modifyVoiceState
import dev.kord.rest.service.patchStageVoiceChannel
Expand All @@ -30,6 +32,18 @@ interface StageChannelBehavior : BaseVoiceChannelBehavior {
return StageChannelBehavior(id, guildId, kord, strategy.supply(kord))
}


suspend fun createStageInstance(topic: String): StageInstance {
val instance = kord.rest.stageInstance.createStageInstance(id, topic)
val data = StageInstanceData.from(instance)

return StageInstance(data, kord, supplier)
}

suspend fun getStageInstanceOrNull(): StageInstance? = supplier.getStageInstanceOrNull(id)

suspend fun getStageInstance(): StageInstance = supplier.getStageInstance(id)

}

/**
Expand Down
19 changes: 19 additions & 0 deletions core/src/main/kotlin/cache/data/StageInstanceData.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package dev.kord.core.cache.data

import dev.kord.common.entity.DiscordStageInstance
import dev.kord.common.entity.Snowflake
import kotlinx.serialization.Serializable

@Serializable
data class StageInstanceData(
val id: Snowflake,
val guildId: Snowflake,
val channelId: Snowflake,
val topic: String
) {
companion object {
fun from(stageInstance: DiscordStageInstance) = with(stageInstance) {
StageInstanceData(id, guildId, channelId, topic)
}
}
}
19 changes: 19 additions & 0 deletions core/src/main/kotlin/entity/StageInstance.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package dev.kord.core.entity

import dev.kord.common.entity.Snowflake
import dev.kord.core.Kord
import dev.kord.core.behavior.StageInstanceBehavior
import dev.kord.core.cache.data.StageInstanceData
import dev.kord.core.supplier.EntitySupplier
import dev.kord.core.supplier.EntitySupplyStrategy

class StageInstance(val data: StageInstanceData, override val kord: Kord, override val supplier: EntitySupplier) : StageInstanceBehavior {
override val id: Snowflake get() = data.id
val guildId: Snowflake get() = data.guildId
override val channelId: Snowflake get() = data.channelId
val topic: String get() = data.topic

override fun withStrategy(strategy: EntitySupplyStrategy<*>): StageInstanceBehavior =
StageInstance(data, kord, strategy.supply(kord))

}
3 changes: 3 additions & 0 deletions core/src/main/kotlin/exception/EntityNotFoundException.kt
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ class EntityNotFoundException : Exception {
fun welcomeScreenNotFound(guildId: Snowflake): Nothing =
throw EntityNotFoundException("Welcome screen for guild $guildId was not found.")

fun stageInstanceNotFound(channelId: Snowflake): Nothing =
throw EntityNotFoundException("Stage instance for channel $channelId was not found")

}

}
2 changes: 2 additions & 0 deletions core/src/main/kotlin/supplier/CacheEntitySupplier.kt
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,8 @@ class CacheEntitySupplier(private val kord: Kord) : EntitySupplier {
}.asFlow().map { Template(it, kord) }
}

override suspend fun getStageInstanceOrNull(channelId: Snowflake): StageInstance? = null

override fun toString(): String {
return "CacheEntitySupplier(cache=$cache)"
}
Expand Down
7 changes: 6 additions & 1 deletion core/src/main/kotlin/supplier/EntitySupplier.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package dev.kord.core.supplier

import dev.kord.common.entity.ChannelType.Unknown
import dev.kord.common.entity.Snowflake
import dev.kord.common.exception.RequestException
import dev.kord.core.entity.*
Expand All @@ -8,7 +9,6 @@ import dev.kord.core.entity.channel.GuildChannel
import dev.kord.core.entity.channel.MessageChannel
import dev.kord.core.exception.EntityNotFoundException
import kotlinx.coroutines.flow.Flow
import dev.kord.common.entity.ChannelType.Unknown

/**
* An abstraction that allows for requesting Discord entities.
Expand Down Expand Up @@ -389,6 +389,11 @@ interface EntitySupplier {
getTemplateOrNull(code) ?: EntityNotFoundException.templateNotFound(code)

fun getTemplates(guildId: Snowflake): Flow<Template>

suspend fun getStageInstanceOrNull(channelId: Snowflake): StageInstance?

suspend fun getStageInstance(channelId: Snowflake): StageInstance =
getStageInstanceOrNull(channelId) ?: EntityNotFoundException.stageInstanceNotFound(channelId)
}


Expand Down
2 changes: 2 additions & 0 deletions core/src/main/kotlin/supplier/FallbackEntitySupplier.kt
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@ private class FallbackEntitySupplier(val first: EntitySupplier, val second: Enti
override fun getTemplates(guildId: Snowflake): Flow<Template> =
first.getTemplates(guildId).switchIfEmpty(second.getTemplates(guildId))

override suspend fun getStageInstanceOrNull(channelId: Snowflake): StageInstance? = first.getStageInstanceOrNull(channelId) ?: second.getStageInstanceOrNull(channelId)


override fun toString(): String {
return "FallbackEntitySupplier(first=$first, second=$second)"
Expand Down
7 changes: 7 additions & 0 deletions core/src/main/kotlin/supplier/RestEntitySupplier.kt
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,13 @@ class RestEntitySupplier(val kord: Kord) : EntitySupplier {
auditLog.getAuditLogs(guildId, request.copy(before = it.value)).auditLogEntries
}

override suspend fun getStageInstanceOrNull(channelId: Snowflake): StageInstance? = catchNotFound {
val instance = kord.rest.stageInstance.getStageInstance(channelId)
val data = StageInstanceData.from(instance)

return StageInstance(data, kord, this)
}

override fun toString(): String {
return "RestEntitySupplier(rest=${kord.rest})"
}
Expand Down
1 change: 1 addition & 0 deletions rest/src/main/kotlin/json/request/ChannelRequests.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package dev.kord.rest.json.request
import dev.kord.common.entity.Overwrite
import dev.kord.common.entity.OverwriteType
import dev.kord.common.entity.Permissions
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.OptionalInt
Expand Down
15 changes: 15 additions & 0 deletions rest/src/main/kotlin/json/request/StageInstanceRequests.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package dev.kord.rest.json.request

import dev.kord.common.entity.Snowflake
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class StageInstanceCreateRequest(
@SerialName("channel_id")
val channelId: Snowflake,
val topic: String
)

@Serializable
data class StageInstanceUpdateRequest(val topic: String)
12 changes: 12 additions & 0 deletions rest/src/main/kotlin/route/Route.kt
Original file line number Diff line number Diff line change
Expand Up @@ -646,6 +646,18 @@ sealed class Route<T>(
object OthersVoiceStatePatch:
Route<Unit>(HttpMethod.Patch, "/guilds/${GuildId}/voice-states/${UserId}", NoStrategy)

object StageInstanceGet :
Route<DiscordStageInstance>(HttpMethod.Get, "/stage-instances/$ChannelId", DiscordStageInstance.serializer())

object StageInstancePost :
Route<DiscordStageInstance>(HttpMethod.Post, "/stage-instances", DiscordStageInstance.serializer())

object StageInstancePatch :
Route<DiscordStageInstance>(HttpMethod.Patch, "/stage-instances/$ChannelId", DiscordStageInstance.serializer())

object StageInstanceDelete :
Route<Unit>(HttpMethod.Delete, "/stage-instances/$ChannelId", NoStrategy)

companion object {
val baseUrl = "https://discord.com/api/$restVersion"
}
Expand Down
1 change: 1 addition & 0 deletions rest/src/main/kotlin/service/RestClient.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ class RestClient(requestHandler: RequestHandler) : RestService(requestHandler) {
val application: ApplicationService = ApplicationService(requestHandler)
val template: TemplateService = TemplateService(requestHandler)
val interaction: InteractionService = InteractionService(requestHandler)
val stageInstance: StageInstanceService = StageInstanceService(requestHandler)

/**
* Sends a request to the given [route]. This function exposes a direct call to the Discord api and allows
Expand Down
39 changes: 39 additions & 0 deletions rest/src/main/kotlin/service/StageInstanceService.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package dev.kord.rest.service

import dev.kord.common.entity.DiscordStageInstance
import dev.kord.common.entity.Snowflake
import dev.kord.rest.json.request.StageInstanceCreateRequest
import dev.kord.rest.json.request.StageInstanceUpdateRequest
import dev.kord.rest.request.RequestHandler
import dev.kord.rest.route.Route

class StageInstanceService(requestHandler: RequestHandler) : RestService(requestHandler) {
suspend fun getStageInstance(channelId: Snowflake): DiscordStageInstance = call(Route.StageInstanceGet) {
keys[Route.ChannelId] = channelId
}

suspend fun createStageInstance(request: StageInstanceCreateRequest): DiscordStageInstance =
call(Route.StageInstancePost) {
body(StageInstanceCreateRequest.serializer(), request)
}

suspend fun updateStageInstance(channelId: Snowflake, request: StageInstanceUpdateRequest): DiscordStageInstance =
call(Route.StageInstancePost) {
keys[Route.ChannelId] = channelId

body(StageInstanceUpdateRequest.serializer(), request)
}

suspend fun deleteStageInstance(channelId: Snowflake): Unit = call(Route.StageInstanceDelete) {
keys[Route.ChannelId] = channelId
}
}

suspend fun StageInstanceService.createStageInstance(channelId: Snowflake, topic: String) = createStageInstance(
StageInstanceCreateRequest(channelId, topic)
)

suspend fun StageInstanceService.updateStageInstance(channelId: Snowflake, topic: String) = updateStageInstance(
channelId,
StageInstanceUpdateRequest(topic)
)

0 comments on commit 4b0d10c

Please sign in to comment.