Skip to content

Commit

Permalink
Add missing Webhook and Interaction functionality (#507)
Browse files Browse the repository at this point in the history
* No shards field on GatewayResponse

* Fix authentication header requirements

* inline shorthands in suppliers

* reason null by default everywhere

* Add routes for webhooks and followup messages

* Add endpoints in WebhookService

* use createGlobalMessageCommandApplicationCommand in Kord

* add getFollowupMessage() to InteractionService, rename createPublicInteractionResponse()

* use builder from InteractionService in ActionInteractionBehavior

* use builder from InteractionService in FollowupMessageBehavior

* use builder from InteractionService in InteractionResponseBehavior

* use builder from InteractionService in PublicFollowupMessageBehavior

* add getFollowupMessageOrNull() and getFollowupMessage()

* getFollowupMessage() in EntitySupplier

* move withStrategy() to interfaces

* get, edit and delete functions for webhook messages

* Restore explicit api after merge

* formatting

* Rename InteractionFollowup to FollowupMessage and better typing for editing followups
  • Loading branch information
lukellmann authored Feb 1, 2022
1 parent 38e4e44 commit f1aecb7
Show file tree
Hide file tree
Showing 24 changed files with 616 additions and 214 deletions.
4 changes: 2 additions & 2 deletions core/src/main/kotlin/Kord.kt
Original file line number Diff line number Diff line change
Expand Up @@ -486,8 +486,8 @@ public class Kord(
builder: MessageCommandCreateBuilder.() -> Unit = {},
): GlobalMessageCommand {
contract { callsInPlace(builder, InvocationKind.EXACTLY_ONCE) }
val request = MessageCommandCreateBuilder(name).apply(builder).toRequest()
val response = rest.interaction.createGlobalApplicationCommand(resources.applicationId, request)
val response =
rest.interaction.createGlobalMessageCommandApplicationCommand(resources.applicationId, name, builder)
val data = ApplicationCommandData.from(response)
return GlobalMessageCommand(data, rest.interaction)
}
Expand Down
60 changes: 40 additions & 20 deletions core/src/main/kotlin/behavior/MessageBehavior.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import dev.kord.core.supplier.EntitySupplier
import dev.kord.core.supplier.EntitySupplyStrategy
import dev.kord.core.supplier.getChannelOf
import dev.kord.core.supplier.getChannelOfOrNull
import dev.kord.rest.builder.message.create.MessageCreateBuilder
import dev.kord.rest.builder.message.create.UserMessageCreateBuilder
import dev.kord.rest.builder.message.modify.UserMessageModifyBuilder
import dev.kord.rest.builder.message.modify.WebhookMessageModifyBuilder
Expand Down Expand Up @@ -47,10 +46,10 @@ public interface MessageBehavior : KordEntity, Strategizable {
public suspend fun getChannelOrNull(): MessageChannel? = supplier.getChannelOfOrNull(channelId)

/**
* Requests to get the this behavior as a [Message].
* Requests to get this behavior as a [Message].
*
* @throws [RequestException] if anything went wrong during the request.
* @throws [EntityNotFoundException] if the message wasn't present.
* @throws RequestException if anything went wrong during the request.
* @throws EntityNotFoundException if the message wasn't present.
*/
public suspend fun asMessage(): Message = supplier.getMessage(channelId = channelId, messageId = id)

Expand Down Expand Up @@ -89,6 +88,18 @@ public interface MessageBehavior : KordEntity, Strategizable {
kord.rest.channel.deleteMessage(channelId = channelId, messageId = id, reason = reason)
}

/**
* Requests to delete this message if it was previously sent from a [Webhook] with the given [webhookId] using the
* [token] for authentication.
*
* If this message is in a thread, [threadId] must be specified.
*
* @throws RestRequestException if something went wrong during the request.
*/
public suspend fun delete(webhookId: Snowflake, token: String, threadId: Snowflake? = null) {
kord.rest.webhook.deleteWebhookMessage(webhookId, token, messageId = id, threadId)
}

/**
* Requests to get all users that have reacted to this message.
*
Expand Down Expand Up @@ -235,8 +246,7 @@ public fun MessageBehavior(
*
* @return The edited [Message].
*
* @throws [RestRequestException] if something went wrong during the request.
* @see editWebhookMessage
* @throws RestRequestException if something went wrong during the request.
*/
public suspend inline fun MessageBehavior.edit(builder: UserMessageModifyBuilder.() -> Unit): Message {
contract {
Expand All @@ -250,37 +260,47 @@ public suspend inline fun MessageBehavior.edit(builder: UserMessageModifyBuilder
return Message(data, kord)
}

@Deprecated(
"'editWebhookMessage' was renamed to 'edit'",
ReplaceWith("this.edit(webhookId, token, threadId = null) { builder() }", "dev.kord.core.behavior.edit"),
DeprecationLevel.ERROR,
)
public suspend inline fun MessageBehavior.editWebhookMessage(
webhookId: Snowflake,
token: String,
builder: WebhookMessageModifyBuilder.() -> Unit,
): Message {
contract { callsInPlace(builder, InvocationKind.EXACTLY_ONCE) }
return edit(webhookId, token, threadId = null, builder)
}

/**
* Requests to edit this message.
* Requests to edit this message if it was previously sent from a [Webhook] with the given [webhookId] using the
* [token] for authentication.
*
* If this message is in a thread, [threadId] must be specified.
*
* @return The edited [Message].
*
* @throws [RestRequestException] if something went wrong during the request.
* @see edit
* @throws RestRequestException if something went wrong during the request.
*/
public suspend inline fun MessageBehavior.editWebhookMessage(
public suspend inline fun MessageBehavior.edit(
webhookId: Snowflake,
token: String,
threadId: Snowflake? = null,
builder: WebhookMessageModifyBuilder.() -> Unit
): Message {
contract {
callsInPlace(builder, InvocationKind.EXACTLY_ONCE)
}

val response =
kord.rest.webhook.editWebhookMessage(
webhookId = webhookId,
messageId = id,
token = token,
builder = builder
)
val response = kord.rest.webhook.editWebhookMessage(webhookId, token, messageId = id, threadId, builder)
val data = MessageData.from(response)

return Message(data, kord)
}

/**
* Request to reply to this message, setting [MessageCreateBuilder.messageReference] to this message [id][MessageBehavior.id].
* Request to reply to this message, setting [messageReference][UserMessageCreateBuilder.messageReference] to this
* message's [id][MessageBehavior.id].
*
* @throws [RestRequestException] if something went wrong during the request.
*/
Expand Down
80 changes: 64 additions & 16 deletions core/src/main/kotlin/behavior/WebhookBehavior.kt
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
package dev.kord.core.behavior

import dev.kord.common.entity.Snowflake
import dev.kord.common.exception.RequestException
import dev.kord.core.Kord
import dev.kord.core.cache.data.MessageData
import dev.kord.core.cache.data.WebhookData
import dev.kord.core.entity.KordEntity
import dev.kord.core.entity.Message
import dev.kord.core.entity.Strategizable
import dev.kord.core.entity.Webhook
import dev.kord.core.exception.EntityNotFoundException
import dev.kord.core.supplier.EntitySupplier
import dev.kord.core.supplier.EntitySupplyStrategy
import dev.kord.rest.builder.message.create.WebhookMessageCreateBuilder
Expand All @@ -26,7 +28,7 @@ public interface WebhookBehavior : KordEntity, Strategizable {
* Requests to delete this webhook, this user must be the creator.
*
* @param reason the reason showing up in the audit log
* @throws [RestRequestException] if something went wrong during the request.
* @throws RestRequestException if something went wrong during the request.
*/
public suspend fun delete(reason: String? = null) {
kord.rest.webhook.deleteWebhook(id, reason)
Expand All @@ -36,12 +38,53 @@ public interface WebhookBehavior : KordEntity, Strategizable {
* Requests to delete this webhook.
*
* @param reason the reason showing up in the audit log
* @throws [RestRequestException] if something went wrong during the request.
* @throws RestRequestException if something went wrong during the request.
*/
public suspend fun delete(token: String, reason: String? = null) {
kord.rest.webhook.deleteWebhookWithToken(id, token, reason)
}

/**
* Requests the [Message] with the given [messageId] previously sent from this webhook using the [token] for
* authentication, returns `null` when the message isn't present.
*
* If the message is in a thread, [threadId] must be specified.
*
* @throws RequestException if something went wrong while retrieving the message.
*/
public suspend fun getMessageOrNull(
token: String,
messageId: Snowflake,
threadId: Snowflake? = null,
): Message? = supplier.getWebhookMessageOrNull(id, token, messageId, threadId)

/**
* Requests the [Message] with the given [messageId] previously sent from this webhook using the [token] for
* authentication.
*
* If the message is in a thread, [threadId] must be specified.
*
* @throws RequestException if something went wrong while retrieving the message.
* @throws EntityNotFoundException if the message is null.
*/
public suspend fun getMessage(
token: String,
messageId: Snowflake,
threadId: Snowflake? = null,
): Message = supplier.getWebhookMessage(id, token, messageId, threadId)

/**
* Requests to delete the [Message] with the given [messageId] previously sent from this webhook using the [token]
* for authentication.
*
* If the message is in a thread, [threadId] must be specified.
*
* @throws RestRequestException if something went wrong during the request.
*/
public suspend fun deleteMessage(token: String, messageId: Snowflake, threadId: Snowflake? = null) {
kord.rest.webhook.deleteWebhookMessage(id, token, messageId, threadId)
}

/**
* Returns a new [WebhookBehavior] with the given [strategy].
*/
Expand Down Expand Up @@ -108,23 +151,21 @@ public suspend inline fun WebhookBehavior.edit(token: String, builder: WebhookMo

/**
* Requests to execute this webhook.
* if [threadId] is specified the execution will occur in that thread.
*
* If [threadId] is specified the execution will occur in that thread.
*
* @throws [RestRequestException] if something went wrong during the request.
*/
public suspend inline fun WebhookBehavior.execute(token: String, threadId: Snowflake? = null, builder: WebhookMessageCreateBuilder.() -> Unit): Message {
public suspend inline fun WebhookBehavior.execute(
token: String,
threadId: Snowflake? = null,
builder: WebhookMessageCreateBuilder.() -> Unit,
): Message {
contract {
callsInPlace(builder, InvocationKind.EXACTLY_ONCE)
}
val response = kord.rest.webhook.executeWebhook(
token = token,
webhookId = id,
wait = true,
threadId = threadId,
builder = builder
)!!

val response = kord.rest.webhook.executeWebhook(id, token, wait = true, threadId, builder)!!
val data = MessageData.from(response)

return Message(data, kord)
}

Expand All @@ -133,11 +174,18 @@ public suspend inline fun WebhookBehavior.execute(token: String, threadId: Snowf
*
* This is a 'fire and forget' variant of [execute]. It will not wait for a response and might not throw an
* Exception if the request wasn't executed.
* if [threadId] is specified the execution will occur in that thread.
*
* If [threadId] is specified the execution will occur in that thread.
*
* @throws [RestRequestException] if something went wrong during the request.
*/
public suspend inline fun WebhookBehavior.executeIgnored(token: String, threadId: Snowflake? = null, builder: WebhookMessageCreateBuilder.() -> Unit) {
public suspend inline fun WebhookBehavior.executeIgnored(
token: String,
threadId: Snowflake? = null,
builder: WebhookMessageCreateBuilder.() -> Unit,
) {
contract {
callsInPlace(builder, InvocationKind.EXACTLY_ONCE)
}
kord.rest.webhook.executeWebhook(token = token, webhookId = id, wait = false, threadId = threadId, builder = builder)
kord.rest.webhook.executeWebhook(id, token, wait = false, threadId, builder)
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@ package dev.kord.core.behavior.interaction
import dev.kord.common.entity.Snowflake
import dev.kord.core.Kord
import dev.kord.core.entity.Message
import dev.kord.core.exception.EntityNotFoundException
import dev.kord.core.supplier.EntitySupplier
import dev.kord.core.supplier.EntitySupplyStrategy
import dev.kord.core.supplier.EntitySupplyStrategy.Companion.rest
import dev.kord.rest.builder.message.create.InteractionResponseCreateBuilder
import dev.kord.rest.request.RestRequestException
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract

Expand All @@ -21,7 +24,7 @@ public interface ActionInteractionBehavior : InteractionBehavior {
* @return [EphemeralInteractionResponseBehavior] Ephemeral acknowledgement of the interaction.
*/
public suspend fun acknowledgeEphemeral(): EphemeralInteractionResponseBehavior {
kord.rest.interaction.acknowledge(id, token, true)
kord.rest.interaction.acknowledge(id, token, ephemeral = true)
return EphemeralInteractionResponseBehavior(applicationId, token, kord)
}

Expand All @@ -31,13 +34,26 @@ public interface ActionInteractionBehavior : InteractionBehavior {
* @return [PublicInteractionResponseBehavior] public acknowledgement of an interaction.
*/
public suspend fun acknowledgePublic(): PublicInteractionResponseBehavior {
kord.rest.interaction.acknowledge(id, token)
kord.rest.interaction.acknowledge(id, token, ephemeral = false)
return PublicInteractionResponseBehavior(applicationId, token, kord)
}

public suspend fun getOriginalInteractionResponse(): Message? {
return EntitySupplyStrategy.rest.supply(kord).getOriginalInteractionOrNull(applicationId, token)
}
/**
* Returns the initial interaction response or `null` if it was not found.
*
* @throws RestRequestException if something went wrong during the request.
*/
public suspend fun getOriginalInteractionResponseOrNull(): Message? =
kord.with(rest).getOriginalInteractionOrNull(applicationId, token)

/**
* Returns the initial interaction response.
*
* @throws RestRequestException if something went wrong during the request.
* @throws EntityNotFoundException if the initial interaction response was not found.
*/
public suspend fun getOriginalInteractionResponse(): Message =
kord.with(rest).getOriginalInteraction(applicationId, token)
}


Expand All @@ -50,13 +66,9 @@ public interface ActionInteractionBehavior : InteractionBehavior {
public suspend inline fun ActionInteractionBehavior.respondPublic(
builder: InteractionResponseCreateBuilder.() -> Unit
): PublicInteractionResponseBehavior {

contract { callsInPlace(builder, InvocationKind.EXACTLY_ONCE) }

val request = InteractionResponseCreateBuilder().apply(builder).toRequest()
kord.rest.interaction.createInteractionResponse(id, token, request)
kord.rest.interaction.createInteractionResponse(id, token, ephemeral = false, builder)
return PublicInteractionResponseBehavior(applicationId, token, kord)

}


Expand All @@ -69,13 +81,9 @@ public suspend inline fun ActionInteractionBehavior.respondPublic(
public suspend inline fun ActionInteractionBehavior.respondEphemeral(
builder: InteractionResponseCreateBuilder.() -> Unit
): EphemeralInteractionResponseBehavior {

contract { callsInPlace(builder, InvocationKind.EXACTLY_ONCE) }
val builder = InteractionResponseCreateBuilder(true).apply(builder)
val request = builder.toRequest()
kord.rest.interaction.createInteractionResponse(id, token, request)
kord.rest.interaction.createInteractionResponse(id, token, ephemeral = true, builder)
return EphemeralInteractionResponseBehavior(applicationId, token, kord)

}

public fun InteractionBehavior(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,15 @@ package dev.kord.core.behavior.interaction

import dev.kord.common.entity.Snowflake
import dev.kord.core.Kord
import dev.kord.core.cache.data.toData
import dev.kord.core.entity.Message
import dev.kord.core.entity.interaction.EphemeralFollowupMessage
import dev.kord.core.supplier.EntitySupplier
import dev.kord.core.supplier.EntitySupplyStrategy
import dev.kord.rest.builder.message.modify.FollowupMessageModifyBuilder
import dev.kord.rest.request.RestRequestException
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract

/**
* The behavior of a [Discord Followup Message](https://discord.com/developers/docs/interactions/slash-commands#followup-messages)
Expand All @@ -17,6 +24,21 @@ public interface EphemeralFollowupMessageBehavior : FollowupMessageBehavior {
}
}

/**
* Requests to edit this followup message.
*
* @return The edited [EphemeralFollowupMessage] of the interaction response.
*
* @throws RestRequestException if something went wrong during the request.
*/
public suspend inline fun EphemeralFollowupMessageBehavior.edit(
builder: FollowupMessageModifyBuilder.() -> Unit,
): EphemeralFollowupMessage {
contract { callsInPlace(builder, InvocationKind.EXACTLY_ONCE) }
val response = kord.rest.interaction.modifyFollowupMessage(applicationId, token, id, builder)
return EphemeralFollowupMessage(Message(response.toData(), kord), applicationId, token, kord)
}


public fun EphemeralFollowupMessageBehavior(
id: Snowflake,
Expand Down
Loading

0 comments on commit f1aecb7

Please sign in to comment.