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 polls #933

Open
wants to merge 27 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 25 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
200 changes: 192 additions & 8 deletions common/api/common.api

Large diffs are not rendered by default.

201 changes: 197 additions & 4 deletions common/api/common.klib.api

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// THIS FILE IS AUTO-GENERATED, DO NOT EDIT!
@file:Suppress(names = arrayOf("IncorrectFormatting", "ReplaceArrayOfWithLiteral",
"SpellCheckingInspection", "GrazieInspection"))

package dev.kord.common.entity

import kotlin.LazyThreadSafetyMode.PUBLICATION
import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializable
import kotlinx.serialization.descriptors.PrimitiveKind
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder

/**
* See [PollLayoutType]s in the
* [Discord Developer Documentation](https://discord.com/developers/docs/resources/poll#layout-type).
*/
@Serializable(with = PollLayoutType.Serializer::class)
public sealed class PollLayoutType(
/**
* The raw value used by Discord.
*/
public val `value`: Int,
) {
final override fun equals(other: Any?): Boolean = this === other ||
(other is PollLayoutType && this.value == other.value)

final override fun hashCode(): Int = value.hashCode()

final override fun toString(): String =
if (this is Unknown) "PollLayoutType.Unknown(value=$value)"
else "PollLayoutType.${this::class.simpleName}"

/**
* An unknown [PollLayoutType].
*
* This is used as a fallback for [PollLayoutType]s that haven't been added to Kord yet.
*/
public class Unknown internal constructor(
`value`: Int,
) : PollLayoutType(value)

/**
* The, uhm, default layout type.
*/
public object Default : PollLayoutType(1)

internal object Serializer : KSerializer<PollLayoutType> {
override val descriptor: SerialDescriptor =
PrimitiveSerialDescriptor("dev.kord.common.entity.PollLayoutType",
PrimitiveKind.INT)

override fun serialize(encoder: Encoder, `value`: PollLayoutType) {
encoder.encodeInt(value.value)
}

override fun deserialize(decoder: Decoder): PollLayoutType = from(decoder.decodeInt())
}

public companion object {
/**
* A [List] of all known [PollLayoutType]s.
*/
public val entries: List<PollLayoutType> by lazy(mode = PUBLICATION) {
listOf(
Default,
)
}

/**
* Returns an instance of [PollLayoutType] with [PollLayoutType.value] equal to the
* specified [value].
*/
public fun from(`value`: Int): PollLayoutType = when (value) {
1 -> Default
else -> Unknown(value)
}
}
}
93 changes: 92 additions & 1 deletion common/src/commonMain/kotlin/entity/DiscordMessage.kt
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,14 @@
],
)

@file:Generate(
INT_KORD_ENUM, name = "PollLayoutType",
docUrl = "https://discord.com/developers/docs/resources/poll#layout-type",
entries = [
Entry("Default", intValue = 1, kDoc = "The, uhm, default layout type."),
],
)

package dev.kord.common.entity

import dev.kord.common.entity.optional.Optional
Expand Down Expand Up @@ -184,6 +192,7 @@ import kotlinx.serialization.Serializable
* @param roleSubscriptionData [RoleSubscription] object data of the role subscription purchase or renewal that prompted this message.
* @param applicationId if the message is a response to an [Interaction][DiscordInteraction], this is the id of the interaction's application
* @param components a list of [components][DiscordComponent] which have been added to this message
* @param poll the poll in this message if any
*/

@Serializable
Expand Down Expand Up @@ -236,7 +245,8 @@ public data class DiscordMessage(
val components: Optional<List<DiscordComponent>> = Optional.Missing(),
val interaction: Optional<DiscordMessageInteraction> = Optional.Missing(),
val thread: Optional<DiscordChannel> = Optional.Missing(),
val position: OptionalInt = OptionalInt.Missing
val position: OptionalInt = OptionalInt.Missing,
val poll: Optional<DiscordPoll> = Optional.Missing()
Copy link
Member

Choose a reason for hiding this comment

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

should also be added to DiscordPartialMessage to be safe

)

/**
Expand Down Expand Up @@ -691,3 +701,84 @@ public data class RoleSubscription(
@SerialName("is_renewal")
val isRenewal: Boolean
)

/**
* Representation of a poll.
*
* @property question the question
* @property answers a list of [answers][Answer]
* @property expiry the [Instant] of the polls end
* @property allowMultiselect Whether a user can select multiple answers
* @property layoutType the [layout type][PollLayoutType] of the poll
*/
@Serializable
public data class DiscordPoll(
val question: Media,
val answers: List<Answer>,
val expiry: Instant?,
@SerialName("allow_multiselect")
val allowMultiselect: Boolean,
@SerialName("layout_type")
val layoutType: PollLayoutType,
val results: Optional<Results> = Optional.Missing(),
) {
/**
* Shared object between answers and questions.
*
* @property text the text
* @property emoji the emoji
*/
@Serializable
public data class Media(
val text: Optional<String> = Optional.Missing(),
val emoji: Optional<DiscordPartialEmoji> = Optional.Missing()
)

/**
* Representation of a possible answer to a poll
*
* @property answerId the ID of the answer
* @property pollMedia the data of the answer
*/
@Serializable
public data class Answer(
@SerialName("answer_id")
val answerId: Int,
@SerialName("poll_media")
val pollMedia: Media
)

@Serializable
public data class Results(
@SerialName("is_finalized")
val isFinalized: Boolean,
@SerialName("answer_counts")
val answerCounts: List<AnswerCount>
) {
/**
* In a nutshell, this contains the number of votes for each answer.
*
* Due to the intricacies of counting at scale, while a poll is in progress the results may not
* be perfectly accurate.
* They usually are accurate, and shouldn't deviate significantly -- it's just difficult to make guarantees.
*
* To compensate for this, after a poll is finished there is a background job which performs a final,
* accurate tally of votes.
* This tally has concluded once [dev.kord.common.entity.DiscordPoll.Results.isFinalized] is `true`.
*
* If [dev.kord.common.entity.DiscordPoll.Results.answerCounts] does not contain an entry for a particular answer,
* then there are no votes for that answer.
*
* @property id the [Answer.answerId]
* @property count the number of votes for this answer
* @property meVoted whether the current user voted for this answer
*/
@Serializable
public data class AnswerCount(
val id: Int,
val count: Int,
@SerialName("me_voted")
val meVoted: Boolean
)
}
}
3 changes: 2 additions & 1 deletion common/src/commonMain/kotlin/entity/Interactions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,8 @@ public data class InteractionCallbackData(
@SerialName("component_type")
val componentType: Optional<ComponentType> = Optional.Missing(),
val values: Optional<List<String>> = Optional.Missing(),
val components: Optional<List<DiscordComponent>> = Optional.Missing()
val components: Optional<List<DiscordComponent>> = Optional.Missing(),
val poll: Optional<DiscordPoll> = Optional.Missing()
Copy link
Member

@lukellmann lukellmann Sep 2, 2024

Choose a reason for hiding this comment

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

Suggested change
val poll: Optional<DiscordPoll> = Optional.Missing()

InteractionCallbackData models https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-object-interaction-data, there is no poll object there (it would be included in DiscordInteraction.message (for components on a poll message, if supported at some point) or InteractionCallbackData.resolved.messages (for message commands etc.) instead)

)

@Serializable(with = Option.Serializer::class)
Expand Down
Loading