Skip to content

Commit

Permalink
Updates to context, consistency, docs and helper funcs.
Browse files Browse the repository at this point in the history
  • Loading branch information
devoxin committed Jul 20, 2023
1 parent 8d256cf commit 14f043a
Show file tree
Hide file tree
Showing 8 changed files with 163 additions and 34 deletions.
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ repositories {
dependencies {
def kotlinVersion = '1.7.22'
def coroutinesVersion = '1.6.4'
def jdaVersion = '5.0.0-beta.3'
def jdaVersion = '5.0.0-beta.12'

implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:$coroutinesVersion"
Expand Down
65 changes: 58 additions & 7 deletions src/main/kotlin/me/devoxin/flight/api/context/Context.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package me.devoxin.flight.api.context

import me.devoxin.flight.api.CommandClient
import me.devoxin.flight.api.entities.DSLMessageCreateBuilder
import me.devoxin.flight.internal.entities.Executable
import net.dv8tion.jda.api.EmbedBuilder
import net.dv8tion.jda.api.JDA
Expand All @@ -10,6 +11,7 @@ import net.dv8tion.jda.api.entities.User
import net.dv8tion.jda.api.entities.channel.middleman.GuildMessageChannel
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel
import net.dv8tion.jda.api.utils.FileUpload
import net.dv8tion.jda.api.utils.messages.MessageCreateBuilder
import net.dv8tion.jda.api.utils.messages.MessageCreateData
import java.util.concurrent.CompletableFuture

Expand Down Expand Up @@ -41,38 +43,87 @@ interface Context {

/**
* Sends "Bot is thinking..." for slash commands, or a typing indicator for message commands.
*
* @param ephemeral
* Whether the response should only be seen by the invoking user.
* This only applies to slash commands.
*/
fun think(ephemeral: Boolean = false): CompletableFuture<*> {
return asSlashContext?.defer0(ephemeral)
?: messageChannel.sendTyping().submit()
}

/**
* Responds to the event. This differs from [send] in that it'll fulfill the "Bot is thinking..." message
* for slash commands.
* Convenience method for replying to either a slash command event, or a message event.
* This will acknowledge, and correctly respond to slash command events, if applicable.
*
* @param content
* The response content to send.
*/
fun respond(content: String): CompletableFuture<*> {
fun reply(content: String): CompletableFuture<*> {
return asSlashContext?.respond0(MessageCreateData.fromContent(content))
?: messageChannel.sendMessage(content).submit()
?: messageChannel.sendMessage(content).setMessageReference(asMessageContext?.message).submit()
}

fun respond(embed: EmbedBuilder.() -> Unit): CompletableFuture<*> {
/**
* Convenience method for replying to either a slash command event, or a message event.
* This will acknowledge, and correctly respond to slash command events, if applicable.
*
* @param embed
* The options to apply to the embed builder.
*/
fun reply(embed: EmbedBuilder.() -> Unit): CompletableFuture<*> {
val create = MessageCreateData.fromEmbeds(EmbedBuilder().apply(embed).build())

return asSlashContext?.respond0(create)
?: messageChannel.sendMessage(create).submit()
}

fun respond(file: FileUpload): CompletableFuture<*> {
/**
* Convenience method for replying to either a slash command event, or a message event.
* This will acknowledge, and correctly respond to slash command events, if applicable.
*
* @param file
* The file to send.
*/
fun reply(file: FileUpload): CompletableFuture<*> {
return asSlashContext?.respond0(MessageCreateData.fromFiles(file))
?: messageChannel.sendFiles(file).submit()
}

fun respond(message: MessageCreateData): CompletableFuture<*> {
/**
* Convenience method for replying to either a slash command event, or a message event.
* This will acknowledge, and correctly respond to slash command events, if applicable.
*
* @param message
* The message data to send.
*/
fun reply(message: MessageCreateData): CompletableFuture<*> {
return asSlashContext?.respond0(message)
?: messageChannel.sendMessage(message).submit()
}

/**
* Convenience method for replying to either a slash command event, or a message event.
* This will acknowledge, and correctly respond to slash command events, if applicable.
*
* @param messageBuilder
* The options to apply when creating a response.
*/
fun reply(messageBuilder: DSLMessageCreateBuilder.() -> Unit): CompletableFuture<*> {
val built = DSLMessageCreateBuilder().apply(messageBuilder).build()

return asSlashContext?.respond0(built)
?: messageChannel.sendMessage(built).submit()
}

/**
* Sends a message to the channel. This has no special handling, and could cause
* problems with slash command events, so use with caution.
*
* @param content
* The response content to send.
*/
fun send(content: String) {
messageChannel.sendMessage(content).submit()
}
Expand Down
16 changes: 8 additions & 8 deletions src/main/kotlin/me/devoxin/flight/api/context/MessageContext.kt
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ class MessageContext(
* The content of the message.
*/
override fun send(content: String) {
send0({ setContent(content) }).submit()
send0({ setContent(content) }).queue()
}

/**
Expand All @@ -55,7 +55,7 @@ class MessageContext(
* The attachment to send.
*/
fun send(attachment: FileUpload) {
send0(null, attachment).submit()
send0(null, attachment).queue()
}

/**
Expand All @@ -65,7 +65,7 @@ class MessageContext(
* Options to apply to the message embed.
*/
fun send(embed: EmbedBuilder.() -> Unit) {
send0({ setEmbeds(EmbedBuilder().apply(embed).build()) }).submit()
send0({ setEmbeds(EmbedBuilder().apply(embed).build()) }).queue()
}

/**
Expand All @@ -77,7 +77,7 @@ class MessageContext(
* The message to send.
*/
fun send(message: MessageCreateData) {
messageChannel.sendMessage(message).submit()
messageChannel.sendMessage(message).queue()
}

/**
Expand All @@ -98,7 +98,7 @@ class MessageContext(
* @param attachment
* The attachment to send.
*
* @return The sent message.
* @return The message that was sent.
*/
suspend fun sendAsync(attachment: FileUpload): Message {
return send0(null, attachment).submit().await()
Expand All @@ -110,7 +110,7 @@ class MessageContext(
* @param embed
* Options to apply to the message embed.
*
* @return The sent message.
* @return The message that was sent.
*/
suspend fun sendAsync(embed: EmbedBuilder.() -> Unit): Message {
return send0({ setEmbeds(EmbedBuilder().apply(embed).build()) }).submit().await()
Expand All @@ -124,7 +124,7 @@ class MessageContext(
* @param message
* The message to send.
*
* @return The sent message.
* @return The message that was sent.
*/
suspend fun sendAsync(message: MessageCreateData): Message {
return messageChannel.sendMessage(message).submit().await()
Expand All @@ -150,7 +150,7 @@ class MessageContext(
* @param message
* The message to send.
*
* @return The sent message.
* @return The message that was sent.
*/
fun sendPrivate(message: MessageCreateData) {
author.openPrivateChannel().submit()
Expand Down
99 changes: 83 additions & 16 deletions src/main/kotlin/me/devoxin/flight/api/context/SlashContext.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package me.devoxin.flight.api.context

import kotlinx.coroutines.future.await
import me.devoxin.flight.api.CommandClient
import me.devoxin.flight.api.entities.DSLMessageCreateBuilder
import me.devoxin.flight.internal.entities.Executable
import net.dv8tion.jda.api.JDA
import net.dv8tion.jda.api.entities.Message
Expand Down Expand Up @@ -35,39 +36,64 @@ class SlashContext(
defer0(ephemeral)
}

suspend fun deferAsync(ephemeral: Boolean = false) = defer0(ephemeral).await()

internal fun defer0(ephemeral: Boolean): CompletableFuture<InteractionHook> {
if (!deferred) { // Idempotency handling
return event.deferReply(ephemeral).submit()
.thenApply { deferred = true; it }
}

return CompletableFuture.completedFuture(event.hook)
suspend fun deferAsync(ephemeral: Boolean = false) {
defer0(ephemeral).await()
}

/**
* This will only call [SlashCommandInteractionEvent.reply] with no special handling.
* Use [respond] or [respondAsync] to handle things such as deferral or already acknowledged events.
*/
fun reply(content: String, ephemeral: Boolean = false) {
respond(MessageCreateData.fromContent(content), ephemeral)
event.reply(content).setEphemeral(ephemeral).queue { replied = true }
}

/**
* This will only call [SlashCommandInteractionEvent.reply] with no special handling.
* Use [respond] or [respondAsync] to handle things such as deferral or already acknowledged events.
*/
fun reply(modal: Modal) {
if (replied) {
throw IllegalStateException("Cannot respond with a Modal to an acknowledged interaction!")
}

event.replyModal(modal).queue { replied = true }
}

/**
* This will only call [SlashCommandInteractionEvent.reply], with no
* special handling to account for acknowledged events.
* This will only call [SlashCommandInteractionEvent.reply] with no special handling.
* Use [respond] or [respondAsync] to handle things such as deferral or already acknowledged events.
*/
fun reply(message: MessageCreateData, ephemeral: Boolean = false) {
event.reply(message).setEphemeral(ephemeral).queue { replied = true }
}

/**
* This will only call [SlashCommandInteractionEvent.reply], with no
* special handling to account for acknowledged events.
* This will only call [SlashCommandInteractionEvent.reply] with no special handling.
* Use [respond] or [respondAsync] to handle things such as deferral or already acknowledged events.
*/
suspend fun replyAsync(message: MessageCreateData, ephemeral: Boolean = false): InteractionHook {
return event.reply(message).setEphemeral(ephemeral).submit().thenApply { replied = true; it }.await()
suspend fun replyAsync(content: String, ephemeral: Boolean = false) {
event.reply(content).setEphemeral(ephemeral).submit().thenAccept { replied = true }.await()
}

/**
* This will only call [SlashCommandInteractionEvent.reply] with no special handling.
* Use [respond] or [respondAsync] to handle things such as deferral or already acknowledged events.
*/
suspend fun replyAsync(modal: Modal) {
if (replied) {
throw IllegalStateException("Cannot respond with a Modal to an acknowledged interaction!")
}

event.replyModal(modal).submit().thenAccept { replied = true }.await()
}

/**
* This will only call [SlashCommandInteractionEvent.reply] with no special handling.
* Use [respond] or [respondAsync] to handle things such as deferral or already acknowledged events.
*/
suspend fun replyAsync(message: MessageCreateData, ephemeral: Boolean = false) {
event.reply(message).setEphemeral(ephemeral).submit().thenAccept { replied = true }.await()
}

/**
Expand All @@ -94,6 +120,47 @@ class SlashContext(
respond0(message, ephemeral)
}

/**
* Convenience method which handles replying the correct way for you.
*
* The [ephemeral] setting is ignored if the interaction is deferred.
* Instead, the ephemeral setting when deferring is used. This is a Discord limitation.
*/
fun respond(builder: DSLMessageCreateBuilder.() -> Unit, ephemeral: Boolean = false) {
val built = DSLMessageCreateBuilder().apply(builder).build()
respond0(built, ephemeral)
}

/**
* Convenience method which handles replying the correct way for you.
*
* The [ephemeral] setting is ignored if the interaction is deferred.
* Instead, the ephemeral setting when deferring is used. This is a Discord limitation.
*/
suspend fun respondAsync(message: MessageCreateData, ephemeral: Boolean = false) {
respond0(message, ephemeral).await()
}

/**
* Convenience method which handles replying the correct way for you.
*
* The [ephemeral] setting is ignored if the interaction is deferred.
* Instead, the ephemeral setting when deferring is used. This is a Discord limitation.
*/
suspend fun respondAsync(builder: DSLMessageCreateBuilder.() -> Unit, ephemeral: Boolean = false) {
val built = DSLMessageCreateBuilder().apply(builder).build()
respond0(built, ephemeral).await()
}

internal fun defer0(ephemeral: Boolean): CompletableFuture<InteractionHook> {
if (!deferred) { // Idempotency handling
return event.deferReply(ephemeral).submit()
.thenApply { deferred = true; it }
}

return CompletableFuture.completedFuture(event.hook)
}

internal fun respond0(message: MessageCreateData, ephemeral: Boolean = false): CompletableFuture<*> {
return when {
replied -> event.hook.sendMessage(message).setEphemeral(ephemeral).submit()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package me.devoxin.flight.api.entities

import net.dv8tion.jda.api.EmbedBuilder
import net.dv8tion.jda.api.utils.messages.MessageCreateBuilder

class DSLMessageCreateBuilder : MessageCreateBuilder() {
fun embed(builder: EmbedBuilder.() -> Unit) {
addEmbeds(EmbedBuilder().apply(builder).build())
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package me.devoxin.flight.internal.entities

import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import me.devoxin.flight.api.context.Context
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class MemberParser : Parser<Member> {
snowflake != null -> ctx.message.mentions.members.firstOrNull { it.user.idLong == snowflake } ?: ctx.guild?.getMemberById(snowflake)
param.length > 5 && param[param.length - 5] == '#' -> {
val tag = param.split("#")
ctx.guild?.memberCache?.find { it.user.name == tag[0] && it.user.discriminator == tag[1] }
ctx.guild?.memberCache?.find { (it.user.discriminator != "0000" && it.user.name == tag[0]) || it.user.asTag == param }
}
else -> ctx.guild?.getMembersByName(param, false)?.firstOrNull()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class UserParser : Parser<User> {
snowflake != null -> ctx.message.mentions.users.firstOrNull { it.idLong == snowflake } ?: ctx.jda.getUserById(snowflake)
param.length > 5 && param[param.length - 5] == '#' -> {
val tag = param.split("#")
ctx.jda.userCache.find { it.name == tag[0] && it.discriminator == tag[1] }
ctx.jda.userCache.find { (it.discriminator != "0000" && it.name == tag[0]) || it.asTag == param }
}
else -> ctx.jda.userCache.find { it.name == param }
}
Expand Down

0 comments on commit 14f043a

Please sign in to comment.