Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
lukellmann committed Nov 2, 2023
1 parent a4f7143 commit 4458638
Show file tree
Hide file tree
Showing 24 changed files with 861 additions and 546 deletions.
1 change: 1 addition & 0 deletions common/api/common.api
Original file line number Diff line number Diff line change
Expand Up @@ -9349,6 +9349,7 @@ public final class dev/kord/common/entity/optional/OptionalKt {
public static final fun mapList (Ldev/kord/common/entity/optional/Optional;Lkotlin/jvm/functions/Function1;)Ldev/kord/common/entity/optional/Optional;
public static final fun mapNotNull (Ldev/kord/common/entity/optional/Optional;Lkotlin/jvm/functions/Function1;)Ldev/kord/common/entity/optional/Optional;
public static final fun mapNullable (Ldev/kord/common/entity/optional/Optional;Lkotlin/jvm/functions/Function1;)Ldev/kord/common/entity/optional/Optional;
public static final fun mapNullableList (Ldev/kord/common/entity/optional/Optional;Lkotlin/jvm/functions/Function1;)Ldev/kord/common/entity/optional/Optional;
public static final fun mapNullableOptional (Ldev/kord/common/entity/optional/Optional;Lkotlin/jvm/functions/Function1;)Ldev/kord/common/entity/optional/Optional;
public static final fun mapNullableSnowflake (Ldev/kord/common/entity/optional/Optional;Lkotlin/jvm/functions/Function1;)Ldev/kord/common/entity/optional/OptionalSnowflake;
public static final fun mapSnowflake (Ldev/kord/common/entity/optional/Optional;Lkotlin/jvm/functions/Function1;)Ldev/kord/common/entity/optional/OptionalSnowflake;
Expand Down
7 changes: 7 additions & 0 deletions common/src/commonMain/kotlin/entity/optional/Optional.kt
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,13 @@ public inline fun <E, T> Optional<List<E>>.mapList(mapper: (E) -> T): Optional<L
is Value -> Value(value.map(mapper))
}

@JvmName("mapNullableList")
public inline fun <E, T> Optional<List<E>?>.mapList(mapper: (E) -> T): Optional<List<T>?> = when (this) {
is Missing -> Missing()
is Null -> Null()
is Value -> Value(value!!.map(mapper))
}

public fun <T> Optional<MutableList<T>>.mapCopy(): Optional<List<T>> = map { mutable -> mutable.toList() }

@JvmName("mapCopyOfMap")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import dev.kord.core.supplier.EntitySupplyStrategy
import dev.kord.rest.builder.message.EmbedBuilder
import dev.kord.rest.builder.message.create.MessageCreateBuilder
import dev.kord.rest.builder.message.create.UserMessageCreateBuilder
import dev.kord.rest.builder.message.create.embed
import dev.kord.rest.builder.message.embed
import dev.kord.rest.request.RestRequestException
import dev.kord.rest.service.RestClient
import kotlinx.coroutines.coroutineScope
Expand Down
272 changes: 177 additions & 95 deletions rest/api/rest.api

Large diffs are not rendered by default.

28 changes: 28 additions & 0 deletions rest/src/commonMain/kotlin/builder/message/AttachmentBuilder.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package dev.kord.rest.builder.message

import dev.kord.common.annotation.KordDsl
import dev.kord.common.entity.Snowflake
import dev.kord.common.entity.optional.Optional
import dev.kord.common.entity.optional.delegate.delegate
import dev.kord.rest.builder.RequestBuilder
import dev.kord.rest.json.request.AttachmentRequest

@KordDsl
public class AttachmentBuilder(private val id: Snowflake) : RequestBuilder<AttachmentRequest> {

private var _filename: Optional<String> = Optional.Missing()

/** The name of the attached file. */
public var filename: String? by ::_filename.delegate()

private var _description: Optional<String> = Optional.Missing()

/** The description for the file (max 1024 characters). */
public var description: String? by ::_description.delegate()

override fun toRequest(): AttachmentRequest = AttachmentRequest(
id = id,
filename = _filename,
description = _description,
)
}
145 changes: 145 additions & 0 deletions rest/src/commonMain/kotlin/builder/message/MessageBuilder.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
package dev.kord.rest.builder.message

import dev.kord.common.annotation.KordDsl
import dev.kord.common.entity.MessageFlag
import dev.kord.common.entity.MessageFlags
import dev.kord.common.entity.Snowflake
import dev.kord.common.entity.optional.Optional
import dev.kord.rest.NamedFile
import dev.kord.rest.builder.component.ActionRowBuilder
import dev.kord.rest.builder.component.MessageComponentBuilder
import dev.kord.rest.request.MultipartRequest
import io.ktor.client.request.forms.*
import kotlin.contracts.InvocationKind.EXACTLY_ONCE
import kotlin.contracts.contract

@KordDsl
public interface MessageBuilder {

/** The message contents (up to 2000 characters). */
public var content: String?

/** Up to 10 embeds (up to 6000 characters). */
public var embeds: MutableList<EmbedBuilder>?

/**
* The mentions in the message that are allowed to trigger a ping.
*
* Setting this to `null` will default to triggering pings for all mentions.
*/
public var allowedMentions: AllowedMentionsBuilder?

/** The components to include with the message.*/
public var components: MutableList<MessageComponentBuilder>?

/** The files to include as attachments. */
public val files: MutableList<NamedFile>

// todo documentation
public var attachments: MutableList<AttachmentBuilder>?

/**
* Optional custom [MessageFlags].
*
* @see suppressEmbeds
*/
public var flags: MessageFlags?

/** Do not include any embeds when serializing this message. */
public var suppressEmbeds: Boolean?

/** Adds a [file][NamedFile] with [name] and [contentProvider] to [files]. */
public fun addFile(name: String, contentProvider: ChannelProvider): NamedFile {
val file = NamedFile(name, contentProvider)
files.add(file)
return file
}
}

/**
* Adds an [embed][EmbedBuilder] configured by [builder] to the [embeds][MessageBuilder.embeds] of the message.
*
* A message can have up to 10 embeds.
*/
public inline fun MessageBuilder.embed(builder: EmbedBuilder.() -> Unit) {
contract { callsInPlace(builder, EXACTLY_ONCE) }
val embed = EmbedBuilder().apply(builder)
embeds?.add(embed) ?: run { embeds = mutableListOf(embed) }
}

/**
* Configures the mentions in the message that are allowed to trigger a ping.
*
* Not calling this function will result in the default behavior (ping for all mentions), calling this function but not
* configuring it before the request is build will result in all mentions being ignored.
*/
public inline fun MessageBuilder.allowedMentions(builder: AllowedMentionsBuilder.() -> Unit = {}) {
contract { callsInPlace(builder, EXACTLY_ONCE) }
val mentions = allowedMentions ?: (AllowedMentionsBuilder().also { allowedMentions = it })
mentions.builder()
}

/**
* Adds an [action row][ActionRowBuilder] configured by the [builder] to the [components][MessageBuilder.components] of
* the message.
*
* A message can have up to five action rows.
*/
public inline fun MessageBuilder.actionRow(builder: ActionRowBuilder.() -> Unit) {
contract { callsInPlace(builder, EXACTLY_ONCE) }
val actionRow = ActionRowBuilder().apply(builder)
components?.add(actionRow) ?: run { components = mutableListOf(actionRow) }
}

// todo documentation
public inline fun MessageBuilder.addFile(
name: String,
contentProvider: ChannelProvider,
builder: AttachmentBuilder.() -> Unit,
): NamedFile {
// see https://discord.com/developers/docs/reference#uploading-files:
// we use the index of a file in the `files` list as `n` in `files[n]`, as implemented in `MultipartRequest.data`
/** (clickable link: [MultipartRequest.data]) */
val file = NamedFile(name, contentProvider)
files.add(file)
val index = files.lastIndex
val attachment = AttachmentBuilder(id = Snowflake(index.toLong())).apply(builder)
attachments?.add(attachment) ?: run { attachments = mutableListOf(attachment) }
return file
}


/** Sets the [flags][MessageBuilder.flags] for the message. */
public inline fun MessageBuilder.messageFlags(builder: MessageFlags.Builder.() -> Unit) {
contract { callsInPlace(builder, EXACTLY_ONCE) }
flags = MessageFlags(builder)
}


internal fun buildMessageFlags(
base: MessageFlags?,
suppressEmbeds: Boolean?,
suppressNotifications: Boolean? = null,
ephemeral: Boolean? = null,
): Optional<MessageFlags> {
fun MessageFlags.Builder.add(add: Boolean?, flag: MessageFlag) {
when (add) {
true -> +flag
false -> -flag
null -> {}
}
}

if (base == null && suppressEmbeds == null && suppressNotifications == null && ephemeral == null) {
return Optional.Missing()
}

val flags = MessageFlags {
if (base != null) +base
add(suppressEmbeds, MessageFlag.SuppressEmbeds)
add(suppressNotifications, MessageFlag.SuppressNotifications)
add(ephemeral, MessageFlag.Ephemeral)
}

return Optional.Value(flags)
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,18 @@ package dev.kord.rest.builder.message.create

import dev.kord.common.annotation.KordDsl
import dev.kord.common.entity.MessageFlags
import dev.kord.common.entity.optional.*
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.common.entity.optional.mapList
import dev.kord.rest.NamedFile
import dev.kord.rest.builder.RequestBuilder
import dev.kord.rest.builder.component.MessageComponentBuilder
import dev.kord.rest.builder.message.AllowedMentionsBuilder
import dev.kord.rest.builder.message.AttachmentBuilder
import dev.kord.rest.builder.message.EmbedBuilder
import dev.kord.rest.builder.message.buildMessageFlags
import dev.kord.rest.json.request.FollowupMessageCreateRequest
import dev.kord.rest.json.request.MultipartFollowupMessageCreateRequest

Expand All @@ -18,36 +24,42 @@ import dev.kord.rest.json.request.MultipartFollowupMessageCreateRequest
public class FollowupMessageCreateBuilder(public val ephemeral: Boolean) :
MessageCreateBuilder,
RequestBuilder<MultipartFollowupMessageCreateRequest> {
// see https://discord.com/developers/docs/interactions/receiving-and-responding#create-followup-message

override var content: String? = null
private var _content: Optional<String> = Optional.Missing()
override var content: String? by ::_content.delegate()

override var tts: Boolean? = null
private var _tts: OptionalBoolean = OptionalBoolean.Missing
override var tts: Boolean? by ::_tts.delegate()

override val embeds: MutableList<EmbedBuilder> = mutableListOf()
private var _embeds: Optional<MutableList<EmbedBuilder>> = Optional.Missing()
override var embeds: MutableList<EmbedBuilder>? by ::_embeds.delegate()

override var allowedMentions: AllowedMentionsBuilder? = null
private var _allowedMentions: Optional<AllowedMentionsBuilder> = Optional.Missing()
override var allowedMentions: AllowedMentionsBuilder? by ::_allowedMentions.delegate()


override val components: MutableList<MessageComponentBuilder> = mutableListOf()
private var _components: Optional<MutableList<MessageComponentBuilder>> = Optional.Missing()
override var components: MutableList<MessageComponentBuilder>? by ::_components.delegate()

override val files: MutableList<NamedFile> = mutableListOf()

private var _attachments: Optional<MutableList<AttachmentBuilder>> = Optional.Missing()
override var attachments: MutableList<AttachmentBuilder>? by ::_attachments.delegate()

override var flags: MessageFlags? = null
override var suppressEmbeds: Boolean? = null
override var suppressNotifications: Boolean? = null

override fun toRequest(): MultipartFollowupMessageCreateRequest {
return MultipartFollowupMessageCreateRequest(
FollowupMessageCreateRequest(
content = Optional(content).coerceToMissing(),
tts = Optional(tts).coerceToMissing().toPrimitive(),
embeds = Optional(embeds).mapList { it.toRequest() },
allowedMentions = Optional(allowedMentions).coerceToMissing().map { it.build() },
components = Optional(components).coerceToMissing().mapList { it.build() },
flags = buildMessageFlags(flags, suppressEmbeds, suppressNotifications, ephemeral)
),
files
)
}

override fun toRequest(): MultipartFollowupMessageCreateRequest = MultipartFollowupMessageCreateRequest(
request = FollowupMessageCreateRequest(
content = _content,
tts = _tts,
embeds = _embeds.mapList { it.toRequest() },
allowedMentions = _allowedMentions.map { it.build() },
components = _components.mapList { it.build() },
attachments = _attachments.mapList { it.toRequest() },
flags = buildMessageFlags(flags, suppressEmbeds, suppressNotifications, ephemeral),
),
files = files.toList(),
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,53 +3,67 @@ package dev.kord.rest.builder.message.create
import dev.kord.common.annotation.KordDsl
import dev.kord.common.entity.MessageFlags
import dev.kord.common.entity.Snowflake
import dev.kord.common.entity.optional.Optional
import dev.kord.common.entity.optional.coerceToMissing
import dev.kord.common.entity.optional.*
import dev.kord.common.entity.optional.delegate.delegate
import dev.kord.common.entity.optional.map
import dev.kord.common.entity.optional.mapList
import dev.kord.rest.NamedFile
import dev.kord.rest.builder.RequestBuilder
import dev.kord.rest.builder.component.MessageComponentBuilder
import dev.kord.rest.builder.message.AllowedMentionsBuilder
import dev.kord.rest.builder.message.AttachmentBuilder
import dev.kord.rest.builder.message.EmbedBuilder
import dev.kord.rest.builder.message.buildMessageFlags
import dev.kord.rest.json.request.ForumThreadMessageRequest
import dev.kord.rest.json.request.MultipartForumThreadMessageCreateRequest

@KordDsl
public class ForumMessageCreateBuilder : MessageCreateBuilder,
RequestBuilder<MultipartForumThreadMessageCreateRequest> {
// see https://discord.com/developers/docs/resources/channel#start-thread-in-forum-or-media-channel

override var content: String? = null
private var _content: Optional<String> = Optional.Missing()
override var content: String? by ::_content.delegate()

override var tts: Boolean? = null
private var _tts: OptionalBoolean = OptionalBoolean.Missing
override var tts: Boolean? by ::_tts.delegate()

override val embeds: MutableList<EmbedBuilder> = mutableListOf()
private var _embeds: Optional<MutableList<EmbedBuilder>> = Optional.Missing()
override var embeds: MutableList<EmbedBuilder>? by ::_embeds.delegate()

override var allowedMentions: AllowedMentionsBuilder? = null
private var _allowedMentions: Optional<AllowedMentionsBuilder> = Optional.Missing()
override var allowedMentions: AllowedMentionsBuilder? by ::_allowedMentions.delegate()

override val components: MutableList<MessageComponentBuilder> = mutableListOf()
private var _components: Optional<MutableList<MessageComponentBuilder>> = Optional.Missing()
override var components: MutableList<MessageComponentBuilder>? by ::_components.delegate()

@Deprecated("Renamed to 'stickers'.", ReplaceWith("this.stickers"), DeprecationLevel.WARNING)
public val stickerIds: MutableList<Snowflake>? get() = null

private var _stickers: Optional<MutableList<Snowflake>> = Optional.Missing()

// todo hoist to supertypes? proper extensions for usability
/** The IDs of up to three stickers to send in the message. */
public var stickers: MutableList<Snowflake>? by ::_stickers.delegate()

override val files: MutableList<NamedFile> = mutableListOf()

private var _stickerIds: Optional<MutableList<Snowflake>> = Optional.Missing()
public val stickerIds: MutableList<Snowflake>? by ::_stickerIds.delegate()
private var _attachments: Optional<MutableList<AttachmentBuilder>> = Optional.Missing()
override var attachments: MutableList<AttachmentBuilder>? by ::_attachments.delegate()

override var flags: MessageFlags? = null
override var suppressEmbeds: Boolean? = null
override var suppressNotifications: Boolean? = null

override fun toRequest(): MultipartForumThreadMessageCreateRequest {
return MultipartForumThreadMessageCreateRequest(
ForumThreadMessageRequest(
content = Optional(content).coerceToMissing(),
embeds = Optional(embeds).mapList { it.toRequest() },
allowedMentions = Optional(allowedMentions).coerceToMissing().map { it.build() },
components = Optional(components).coerceToMissing().mapList { it.build() },
stickerIds = _stickerIds,
flags = buildMessageFlags(flags, suppressEmbeds, suppressNotifications),
),
files
)
}
override fun toRequest(): MultipartForumThreadMessageCreateRequest = MultipartForumThreadMessageCreateRequest(
request = ForumThreadMessageRequest(
content = _content,
tts = _tts,
embeds = _embeds.mapList { it.toRequest() },
allowedMentions = _allowedMentions.map { it.build() },
components = _components.mapList { it.build() },
stickerIds = _stickers.mapCopy(),
attachments = _attachments.mapList { it.toRequest() },
flags = buildMessageFlags(flags, suppressEmbeds, suppressNotifications),
),
files = files.toList(),
)
}
Loading

0 comments on commit 4458638

Please sign in to comment.