Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
18 changes: 10 additions & 8 deletions common/api/common.api

Large diffs are not rendered by default.

33 changes: 18 additions & 15 deletions common/api/common.klib.api

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions common/src/commonMain/kotlin/entity/DiscordChannel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,8 @@ public data class DiscordChannel(
@SerialName("video_quality_mode")
val videoQualityMode: Optional<VideoQualityMode> = Optional.Missing(),
val permissions: Optional<Permissions> = Optional.Missing(),
@SerialName("app_permissions")
val appPermissions: Optional<Permissions> = Optional.Missing(),
@SerialName("message_count")
val messageCount: OptionalInt = OptionalInt.Missing,
@SerialName("member_count")
Expand Down
19 changes: 11 additions & 8 deletions core/api/core.api

Large diffs are not rendered by default.

35 changes: 20 additions & 15 deletions core/api/core.klib.api

Large diffs are not rendered by default.

56 changes: 44 additions & 12 deletions core/src/commonMain/kotlin/Util.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,17 @@ package dev.kord.core

import dev.kord.common.entity.ComponentType
import dev.kord.common.entity.Snowflake
import dev.kord.common.entity.optional.orEmpty
import dev.kord.core.cache.data.ChatComponentData
import dev.kord.core.cache.data.ComponentData
import dev.kord.core.cache.data.SelectComponentData
import dev.kord.core.entity.Message
import dev.kord.core.entity.channel.thread.ThreadChannel
import dev.kord.core.entity.component.ButtonComponent
import dev.kord.core.entity.component.ChannelSelectComponent
import dev.kord.core.entity.component.MentionableSelectComponent
import dev.kord.core.entity.component.RoleSelectComponent
import dev.kord.core.entity.component.SelectMenuComponent
import dev.kord.core.entity.component.StringSelectComponent
import dev.kord.core.entity.component.UserSelectComponent
import dev.kord.core.event.Event
Expand All @@ -30,6 +34,7 @@ import dev.kord.rest.json.JsonErrorCode
import dev.kord.rest.request.RestRequestException
import dev.kord.rest.route.Position
import kotlinx.coroutines.flow.*
import kotlin.collections.mapNotNull
import kotlin.time.Clock
import kotlin.time.Instant
import kotlin.contracts.InvocationKind
Expand Down Expand Up @@ -134,8 +139,8 @@ internal fun <Batch : Collection<Item>, Item : Any, Direction : Position.BeforeO


/**
* Discord returns values in order newest -> oldest (big -> small) (confirmed for messages),
* meaning that the first item returned is the one last created (youngest) in the batch.
* Returns the item with the youngest id in the collection, or null if empty.
* Assumes that the items are ordered either chronologically or reverse-chronologically.
*/
internal fun <T> youngestItem(idSelector: (T) -> Snowflake): (Collection<T>) -> T? = function@{
if (it.size <= 1) return@function it.firstOrNull()
Expand All @@ -151,8 +156,8 @@ internal fun <T> youngestItem(idSelector: (T) -> Snowflake): (Collection<T>) ->
}

/**
* Discord returns values in order oldest -> newest (big -> small) (confirmed for messages),
* meaning that the last item returned is the one first created (oldest) in the batch.
* Returns the item with the oldest id in the collection, or null if empty.
* Assumes that the items are ordered either chronologically or reverse-chronologically.
*/
internal fun <T> oldestItem(idSelector: (T) -> Snowflake): (Collection<T>) -> T? = function@{
if (it.size <= 1) return@function it.firstOrNull()
Expand Down Expand Up @@ -469,13 +474,40 @@ public fun Intents.Builder.enableEvent(event: KClass<out Event>): Unit = when (e
internal fun hash(vararg values: Any?) = values.contentHashCode()

/**
* Takes a [ComponentData] object and returns the relevant select menu or null if the component is not a select menu
* Takes a [ComponentData] child object from an action row and returns the relevant select menu
* or null if the component is not a select menu
*/
internal fun componentToSelectMenu(component: SelectComponentData) = when (component.type) {
ComponentType.StringSelect -> StringSelectComponent(component)
ComponentType.RoleSelect -> RoleSelectComponent(component)
ComponentType.UserSelect -> UserSelectComponent(component)
ComponentType.MentionableSelect -> MentionableSelectComponent(component)
ComponentType.ChannelSelect -> ChannelSelectComponent(component)
else -> null
internal fun actionRowChildComponentToSelectMenu(component: ComponentData): SelectMenuComponent? {
if (component !is SelectComponentData) return null
return when (component.type) {
ComponentType.StringSelect -> StringSelectComponent(component)
ComponentType.RoleSelect -> RoleSelectComponent(component)
ComponentType.UserSelect -> UserSelectComponent(component)
ComponentType.MentionableSelect -> MentionableSelectComponent(component)
ComponentType.ChannelSelect -> ChannelSelectComponent(component)
else -> throw RuntimeException("Unsupported select component type in action row: ${component.type}")
}
}

/**
* Takes a [ComponentData] object from a message and appends to [outputList] all the buttons in that component
* (including itself if applicable). Resolves recursively for Container components.
*/
internal fun getButtonsInMessageComponent(
component: ComponentData,
outputList: MutableList<ButtonComponent>
) {
if (component.type == ComponentType.Container) {
for (innerComponent in component.components.orEmpty()) {
getButtonsInMessageComponent(innerComponent, outputList)
}
} else if (component.type == ComponentType.ActionRow) {
outputList.addAll(component.components.orEmpty().mapNotNull {
if (it.type == ComponentType.Button) ButtonComponent(it as ChatComponentData) else null
})
} else if (component.type == ComponentType.Section) {
// Accessory is required in section
val accessory = (component as ChatComponentData).accessory.value!!
if (accessory.type == ComponentType.Button) outputList.add(ButtonComponent(accessory))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,9 @@ public interface MessageChannelBehavior : ChannelBehavior, Strategizable {
/**
* Requests to get all messages in this channel that were created **before** [messageId].
*
* Messages retrieved by this function will be emitted in reverse-chronological older (newest -> oldest).
* If using [EntitySupplyStrategy.rest], messages retrieved by this function will be emitted in
* **reverse-chronological** order (newest -> oldest). Otherwise, there is no guarantee of ordering.
* To force rest behavior, see [withStrategy].
*
* The flow may use paginated requests to supply messages, [limit] will limit the maximum number of messages
* supplied and may optimize the batch size accordingly. `null` means no limit.
Expand All @@ -149,7 +151,9 @@ public interface MessageChannelBehavior : ChannelBehavior, Strategizable {
/**
* Requests to get all messages in this channel that were created **after** [messageId].
*
* Messages retrieved by this function will be emitted in chronological older (oldest -> newest).
* If using [EntitySupplyStrategy.rest], messages retrieved by this function will be emitted in
* **chronological** order (oldest -> newest). Otherwise, there is no guarantee of ordering.
* To force rest behavior, see [withStrategy].
*
* The flow may use paginated requests to supply messages, [limit] will limit the maximum number of messages
* supplied and may optimize the batch size accordingly. `null` means no limit.
Expand All @@ -172,7 +176,9 @@ public interface MessageChannelBehavior : ChannelBehavior, Strategizable {
/**
* Requests to get [Message]s around (both older and newer) the [messageId].
*
* Messages retrieved by this function will be emitted in chronological older (oldest -> newest).
* If using [EntitySupplyStrategy.rest], messages retrieved by this function will be emitted in
* **reverse-chronological** order (newest -> oldest). Otherwise, there is no guarantee of ordering.
* To force rest behavior, see [withStrategy].
*
* Unlike [getMessagesAfter] and [getMessagesBefore], this flow can return **a maximum of 100 messages**.
* As such, the accepted range of [limit] is reduced to 1..100.
Expand Down
2 changes: 2 additions & 0 deletions core/src/commonMain/kotlin/cache/data/ChannelData.kt
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ public data class ChannelData(
val rtcRegion: Optional<String?> = Optional.Missing(),
val videoQualityMode: Optional<VideoQualityMode> = Optional.Missing(),
val permissions: Optional<Permissions> = Optional.Missing(),
val appPermissions: Optional<Permissions> = Optional.Missing(),
val threadMetadata: Optional<ThreadMetadataData> = Optional.Missing(),
val messageCount: OptionalInt = OptionalInt.Missing,
val memberCount: OptionalInt = OptionalInt.Missing,
Expand Down Expand Up @@ -79,6 +80,7 @@ public data class ChannelData(
rtcRegion,
videoQualityMode,
permissions,
appPermissions,
threadMetadata.map { ThreadMetadataData.from(it) },
messageCount,
memberCount,
Expand Down
35 changes: 9 additions & 26 deletions core/src/commonMain/kotlin/entity/Message.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,12 @@ import dev.kord.common.entity.optional.unwrap
import dev.kord.common.entity.optional.value
import dev.kord.common.exception.RequestException
import dev.kord.core.Kord
import dev.kord.core.actionRowChildComponentToSelectMenu
import dev.kord.core.behavior.MessageBehavior
import dev.kord.core.behavior.UserBehavior
import dev.kord.core.behavior.channel.ChannelBehavior
import dev.kord.core.behavior.interaction.response.InteractionResponseBehavior
import dev.kord.core.cache.data.ChannelData
import dev.kord.core.cache.data.ChatComponentData
import dev.kord.core.cache.data.InteractionMetadataData
import dev.kord.core.cache.data.MessageData
import dev.kord.core.cache.data.MessageInteractionData
import dev.kord.core.cache.data.SelectComponentData
import dev.kord.core.cache.data.toData
import dev.kord.core.cache.data.*
import dev.kord.core.entity.application.ApplicationCommand
import dev.kord.core.entity.channel.Channel
import dev.kord.core.entity.channel.GuildChannel
Expand All @@ -30,8 +25,8 @@ import dev.kord.core.entity.component.SelectMenuComponent
import dev.kord.core.entity.interaction.ActionInteraction
import dev.kord.core.entity.interaction.followup.FollowupMessage
import dev.kord.core.exception.EntityNotFoundException
import dev.kord.core.getButtonsInMessageComponent
import dev.kord.core.hash
import dev.kord.core.componentToSelectMenu
import dev.kord.core.supplier.EntitySupplier
import dev.kord.core.supplier.EntitySupplyStrategy
import dev.kord.core.supplier.getChannelOf
Expand Down Expand Up @@ -367,19 +362,7 @@ public class Message(
get() {
val list = mutableListOf<ButtonComponent>()
for (component in data.components.orEmpty()) {
if (component.type == ComponentType.Container) {
for (container in component.components.orEmpty()) {
if (container.type != ComponentType.ActionRow) continue

list.addAll(container.components.orEmpty().mapNotNull {
if (it.type == ComponentType.Button) ButtonComponent(it as ChatComponentData) else null
})
}
} else if (component.type == ComponentType.ActionRow) {
list.addAll(component.components.orEmpty().mapNotNull {
if (it.type == ComponentType.Button) ButtonComponent(it as ChatComponentData) else null
})
}
getButtonsInMessageComponent(component, list)
}

return list
Expand All @@ -391,16 +374,16 @@ public class Message(
val list = mutableListOf<SelectMenuComponent>()
for (component in data.components.orEmpty()) {
if (component.type == ComponentType.Container) {
for (container in component.components.orEmpty()) {
if (container.type != ComponentType.ActionRow) continue
for (innerComponent in component.components.orEmpty()) {
if (innerComponent.type != ComponentType.ActionRow) continue

list.addAll(container.components.orEmpty().mapNotNull {
componentToSelectMenu(it as SelectComponentData)
list.addAll(innerComponent.components.orEmpty().mapNotNull {
actionRowChildComponentToSelectMenu(it)
})
}
} else if (component.type == ComponentType.ActionRow) {
list.addAll(component.components.orEmpty().mapNotNull {
componentToSelectMenu(it as SelectComponentData)
actionRowChildComponentToSelectMenu(it)
})
}
}
Expand Down
2 changes: 2 additions & 0 deletions core/src/commonMain/kotlin/entity/channel/ResolvedChannel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ public class ResolvedChannel(

public val permissions: Permissions get() = data.permissions.value!!

public val appPermissions: Permissions? get() = data.appPermissions.value

override suspend fun asChannel(): Channel = this

override suspend fun asChannelOrNull(): Channel = this
Expand Down
7 changes: 4 additions & 3 deletions core/src/commonMain/kotlin/supplier/RestEntitySupplier.kt
Original file line number Diff line number Diff line change
Expand Up @@ -121,18 +121,19 @@ public class RestEntitySupplier(public val kord: Kord) : EntitySupplier {
Message(channel.getMessage(channelId = channelId, messageId = messageId).toData(), kord)
}

// maxBatchSize: see https://discord.com/developers/docs/resources/channel#get-channel-messages
// maxBatchSize: see https://docs.discord.com/developers/resources/message#get-channel-messages
override fun getMessagesAfter(messageId: Snowflake, channelId: Snowflake, limit: Int?): Flow<Message> =
limitedPagination(limit, maxBatchSize = 100) { batchSize ->
paginateForwards(batchSize, start = messageId, idSelector = { it.id }) { after ->
channel.getMessages(channelId, position = after, limit = batchSize)
// Discord returns in reverse-chronological order, so reverse for chronological flow
channel.getMessages(channelId, position = after, limit = batchSize).reversed()
}
}.map {
val data = MessageData.from(it)
Message(data, kord)
}

// maxBatchSize: see https://discord.com/developers/docs/resources/channel#get-channel-messages
// maxBatchSize: see https://docs.discord.com/developers/resources/message#get-channel-messages
override fun getMessagesBefore(messageId: Snowflake, channelId: Snowflake, limit: Int?): Flow<Message> =
limitedPagination(limit, maxBatchSize = 100) { batchSize ->
paginateBackwards(batchSize, start = messageId, idSelector = { it.id }) { before ->
Expand Down
Loading