Skip to content

Commit

Permalink
Interaction changes (#201)
Browse files Browse the repository at this point in the history
* remove redundant map structures

* code cleanup

* [WIP] - Rewrite Interaction Commands

* Fix incorrect name reference

* abstract rootName instead of name

* Move documents to declaration and rename Command

* More docs

* correct sub-commands in docs

* Fix filtered no-arg sub-commands

* Add interaction serialization tests

* Add interaction type testing

* mark test class with KordPreview

* WIP: Add resolvables

* WIP: Use flows for resolved objects

This most likely will be changed to a set or back to a map

* Resolvables first prototype

* Rename OptionValue to DiscordOptionValue

* Create a core OptionValue to hold resolvable entities

* Hold resolvable entities in the interaction command

* consider MemberOption a UserOption

* Serialize message flags as longs

* Add Ephemeral flag

* Change interaction response types

discord/discord-api-docs#2615

* Use deferred response type for acknowledgement

* Revert Message flags to ints

* Fix incorrect endpoint call

* Add multi-command put support

* add mapeValues for maps data structure

* Correct the json representations for new interactions

* Add DmInteractions and GuildInteractions

* Use put instead of post for bulk command registration

* remove resolved field from interaction object

* make deaf and mute optional

* Core representations for bulk command end points

* Code cleanup

* Fix serialization for roles

* Expose allowedMentions to followup builder (#187)

* Apply suggestions

* Code cleanup

* Fix predicate and apply few tweaks

Co-authored-by: Michael Rittmeister <michael@rittmeister.in>
  • Loading branch information
HopeBaron and DRSchlaubi authored Mar 5, 2021
1 parent e790687 commit 62ab68e
Show file tree
Hide file tree
Showing 26 changed files with 947 additions and 187 deletions.
4 changes: 3 additions & 1 deletion common/src/main/kotlin/entity/DiscordMessage.kt
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,9 @@ enum class MessageFlag(val code: Int) {
SourceMessageDeleted(8),

/* This message came from the urgent message system. */
Urgent(16);
Urgent(16),

Ephemeral(64);
}

@Serializable(with = MessageFlags.Serializer::class)
Expand Down
122 changes: 65 additions & 57 deletions common/src/main/kotlin/entity/Interactions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,27 +18,27 @@ val kordLogger = KotlinLogging.logger { }
@Serializable
@KordPreview
data class DiscordApplicationCommand(
val id: Snowflake,
@SerialName("application_id")
val applicationId: Snowflake,
val name: String,
val description: String,
@SerialName("guild_id")
val guildId: OptionalSnowflake = OptionalSnowflake.Missing,
val options: Optional<List<ApplicationCommandOption>> = Optional.Missing(),
val id: Snowflake,
@SerialName("application_id")
val applicationId: Snowflake,
val name: String,
val description: String,
@SerialName("guild_id")
val guildId: OptionalSnowflake = OptionalSnowflake.Missing,
val options: Optional<List<ApplicationCommandOption>> = Optional.Missing(),
)

@Serializable
@KordPreview
class ApplicationCommandOption(
val type: ApplicationCommandOptionType,
val name: String,
val description: String,
val default: OptionalBoolean = OptionalBoolean.Missing,
val required: OptionalBoolean = OptionalBoolean.Missing,
@OptIn(KordExperimental::class)
val choices: Optional<List<Choice<@Serializable(NotSerializable::class) Any?>>> = Optional.Missing(),
val options: Optional<List<ApplicationCommandOption>> = Optional.Missing(),
val type: ApplicationCommandOptionType,
val name: String,
val description: String,
val default: OptionalBoolean = OptionalBoolean.Missing,
val required: OptionalBoolean = OptionalBoolean.Missing,
@OptIn(KordExperimental::class)
val choices: Optional<List<Choice<@Serializable(NotSerializable::class) Any?>>> = Optional.Missing(),
val options: Optional<List<ApplicationCommandOption>> = Optional.Missing(),
)

/**
Expand Down Expand Up @@ -142,19 +142,29 @@ sealed class Choice<out T> {
}
}

@Serializable
@KordPreview
data class ResolvedObjects(
val members: Optional<Map<Snowflake, DiscordGuildMember>> = Optional.Missing(),
val users: Optional<Map<Snowflake, DiscordUser>> = Optional.Missing(),
val roles: Optional<Map<Snowflake, DiscordRole>> = Optional.Missing(),
val channels: Optional<Map<Snowflake, DiscordChannel>> = Optional.Missing()
)

@Serializable
@KordPreview
data class DiscordInteraction(
val id: Snowflake,
val type: InteractionType,
val data: DiscordApplicationCommandInteractionData,
@SerialName("guild_id")
val guildId: Snowflake,
@SerialName("channel_id")
val channelId: Snowflake,
val member: DiscordInteractionGuildMember,
val token: String,
val version: Int,
val id: Snowflake,
val type: InteractionType,
val data: DiscordApplicationCommandInteractionData,
@SerialName("guild_id")
val guildId: OptionalSnowflake = OptionalSnowflake.Missing,
@SerialName("channel_id")
val channelId: Snowflake,
val member: Optional<DiscordInteractionGuildMember> = Optional.Missing(),
val user: Optional<DiscordUser> = Optional.Missing(),
val token: String,
val version: Int,
)

@Serializable(InteractionType.Serializer::class)
Expand All @@ -164,7 +174,7 @@ sealed class InteractionType(val type: Int) {
object ApplicationCommand : InteractionType(2)
class Unknown(type: Int) : InteractionType(type)

override fun toString(): String = when(this){
override fun toString(): String = when (this) {
Ping -> "InteractionType.Ping($type)"
ApplicationCommand -> "InteractionType.ApplicationCommand($type)"
is Unknown -> "InteractionType.Unknown($type)"
Expand Down Expand Up @@ -194,9 +204,10 @@ sealed class InteractionType(val type: Int) {
@Serializable
@KordPreview
data class DiscordApplicationCommandInteractionData(
val id: Snowflake,
val name: String,
val options: Optional<List<Option>> = Optional.Missing()
val id: Snowflake,
val name: String,
val resolved: Optional<ResolvedObjects> = Optional.Missing(),
val options: Optional<List<Option>> = Optional.Missing()
)

@Serializable(with = Option.Serializer::class)
Expand Down Expand Up @@ -233,7 +244,7 @@ sealed class Option {
}

jsonValue?.let { value -> // name + value == command option, i.e. an argument
return CommandArgument(name, OptionValue(value))
return CommandArgument(name, DiscordOptionValue(value))
}

if (jsonOptions == null) { // name -value -options == can only be sub command
Expand All @@ -247,7 +258,8 @@ sealed class Option {
return SubCommand(name, Optional(emptyList()))
}

val onlyArguments = nestedOptions.all { it is CommandArgument } //only subcommand can have options at this point
val onlyArguments =
nestedOptions.all { it is CommandArgument } //only subcommand can have options at this point
if (onlyArguments) return SubCommand(name, Optional(nestedOptions.filterIsInstance<CommandArgument>()))

val onlySubCommands = nestedOptions.all { it is SubCommand } //only groups can have options at this point
Expand All @@ -265,16 +277,16 @@ sealed class Option {
@Serializable
@KordPreview
data class SubCommand(
override val name: String,
val options: Optional<List<CommandArgument>> = Optional.Missing()
override val name: String,
val options: Optional<List<CommandArgument>> = Optional.Missing()
) : Option()

@Serializable
@KordPreview
data class CommandArgument(
override val name: String,
@OptIn(KordExperimental::class)
val value: OptionValue<@Serializable(NotSerializable::class) Any?>,
override val name: String,
@OptIn(KordExperimental::class)
val value: DiscordOptionValue<@Serializable(NotSerializable::class) Any?>,
) : Option()

@Serializable
Expand All @@ -284,32 +296,32 @@ data class CommandGroup(
val options: Optional<List<SubCommand>> = Optional.Missing(),
) : Option()

@Serializable(OptionValue.OptionValueSerializer::class)
@Serializable(DiscordOptionValue.OptionValueSerializer::class)
@KordPreview
sealed class OptionValue<out T>(val value: T) {
class IntValue(value: Int) : OptionValue<Int>(value)
class StringValue(value: String) : OptionValue<String>(value)
class BooleanValue(value: Boolean) : OptionValue<Boolean>(value)
sealed class DiscordOptionValue<out T>(val value: T) {
class IntValue(value: Int) : DiscordOptionValue<Int>(value)
class StringValue(value: String) : DiscordOptionValue<String>(value)
class BooleanValue(value: Boolean) : DiscordOptionValue<Boolean>(value)

override fun toString(): String = when(this){
override fun toString(): String = when (this) {
is IntValue -> "OptionValue.IntValue($value)"
is StringValue -> "OptionValue.StringValue($value)"
is BooleanValue -> "OptionValue.BooleanValue($value)"
}

companion object {
operator fun invoke(value: JsonPrimitive): OptionValue<Any> = when {
operator fun invoke(value: JsonPrimitive): DiscordOptionValue<Any> = when {
value.isString -> StringValue(value.content)
value.booleanOrNull != null -> BooleanValue(value.boolean)
value.intOrNull != null -> IntValue(value.int)
else -> throw SerializationException("unknown value type for option")
}
}

internal class OptionValueSerializer<T>(serializer: KSerializer<T>) : KSerializer<OptionValue<*>> {
internal class OptionValueSerializer<T>(serializer: KSerializer<T>) : KSerializer<DiscordOptionValue<*>> {
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("OptionValue", PrimitiveKind.STRING)

override fun deserialize(decoder: Decoder): OptionValue<*> {
override fun deserialize(decoder: Decoder): DiscordOptionValue<*> {
val value = (decoder as JsonDecoder).decodeJsonElement().jsonPrimitive
return when {
value.isString -> StringValue(value.toString())
Expand All @@ -318,7 +330,7 @@ sealed class OptionValue<out T>(val value: T) {
}
}

override fun serialize(encoder: Encoder, value: OptionValue<*>) {
override fun serialize(encoder: Encoder, value: DiscordOptionValue<*>) {
when (value) {
is IntValue -> encoder.encodeInt(value.value)
is StringValue -> encoder.encodeString(value.value)
Expand All @@ -330,23 +342,23 @@ sealed class OptionValue<out T>(val value: T) {


@KordPreview
fun OptionValue<*>.int(): Int {
fun DiscordOptionValue<*>.int(): Int {
return value as? Int ?: error("$value wasn't an Int.")
}


@KordPreview
fun OptionValue<*>.string(): String {
fun DiscordOptionValue<*>.string(): String {
return value.toString()
}

@KordPreview
fun OptionValue<*>.boolean(): Boolean {
fun DiscordOptionValue<*>.boolean(): Boolean {
return value as? Boolean ?: error("$value wasn't a Boolean.")
}

@KordPreview
fun OptionValue<*>.snowflake(): Snowflake {
fun DiscordOptionValue<*>.snowflake(): Snowflake {
val id = string().toLongOrNull() ?: error("$value wasn't a Snowflake")
return Snowflake(id)
}
Expand All @@ -356,10 +368,8 @@ fun OptionValue<*>.snowflake(): Snowflake {
@KordPreview
sealed class InteractionResponseType(val type: Int) {
object Pong : InteractionResponseType(1)
object Acknowledge : InteractionResponseType(2)
object ChannelMessage : InteractionResponseType(3)
object ChannelMessageWithSource : InteractionResponseType(4)
object ACKWithSource : InteractionResponseType(5)
object DeferredChannelMessageWithSource : InteractionResponseType(5)
class Unknown(type: Int) : InteractionResponseType(type)

companion object;
Expand All @@ -372,10 +382,8 @@ sealed class InteractionResponseType(val type: Int) {
override fun deserialize(decoder: Decoder): InteractionResponseType {
return when (val type = decoder.decodeInt()) {
1 -> Pong
2 -> Acknowledge
3 -> ChannelMessage
4 -> ChannelMessageWithSource
5 -> ACKWithSource
5 -> DeferredChannelMessageWithSource
else -> Unknown(type)
}
}
Expand Down
6 changes: 2 additions & 4 deletions common/src/main/kotlin/entity/Member.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ data class DiscordGuildMember(
val joinedAt: String,
@SerialName("premium_since")
val premiumSince: Optional<String?> = Optional.Missing(),
val deaf: Boolean,
val mute: Boolean,
val deaf: OptionalBoolean = OptionalBoolean.Missing,
val mute: OptionalBoolean = OptionalBoolean.Missing,
val pending: OptionalBoolean = OptionalBoolean.Missing
)

Expand All @@ -37,8 +37,6 @@ data class DiscordInteractionGuildMember(
val joinedAt: String,
@SerialName("premium_since")
val premiumSince: Optional<String?> = Optional.Missing(),
val deaf: Boolean,
val mute: Boolean,
val permissions: Permissions,
)

Expand Down
17 changes: 17 additions & 0 deletions common/src/main/kotlin/entity/optional/Optional.kt
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,13 @@ inline fun <E, T> Optional<List<E>>.mapList(mapper: (E) -> T): Optional<List<T>>
}


@Suppress("UNCHECKED_CAST")
inline fun <K, V, R> Optional<Map<K, V>>.mapValues(mapper: (Map.Entry<K, V>) -> R): Optional<Map<K, R>> = when (this) {
is Missing, is Null<*> -> this as Optional<Map<K, R>>
is Value -> Value(value.mapValues(mapper))
}


@Suppress("UNCHECKED_CAST")
inline fun <E> Optional<List<E>>.filterList(mapper: (E) -> Boolean): Optional<List<E>> = when (this) {
is Missing, is Null<*> -> this
Expand Down Expand Up @@ -251,6 +258,16 @@ inline fun <E: Any, T> Optional<E?>.mapNotNull(mapper: (E) -> T): Optional<T?> =
is Value -> Optional(mapper(value!!))
}

inline fun <E> Optional<List<E>>.firstOrNull(predicate: (E) -> Boolean) : E? = when(this){
is Missing, is Null<*> -> null
is Value -> value.firstOrNull(predicate)
}


inline fun <E> Optional<List<E>>.first(predicate: (E) -> Boolean) : E = firstOrNull(predicate)!!



inline fun <E : Any> Optional<E>.mapSnowflake(mapper: (E) -> Snowflake): OptionalSnowflake = when (this) {
is Missing, is Null<*> -> OptionalSnowflake.Missing
is Value -> OptionalSnowflake.Value(mapper(value))
Expand Down
Loading

0 comments on commit 62ab68e

Please sign in to comment.