Collection of useful Kotlin extensions for JDA. Great in combination with kotlinx-coroutines and jda-reactor.
This is a fork of MinnDevelopment/jda-ktx, with changes that I am using for my bot.
repositories {
mavenCentral()
maven("https://m2.dv8tion.net/releases")
}
dependencies {
implementation("net.dv8tion:JDA:${JDA_VERSION}")
implementation("gay.solonovamax:jda-ktx:${JDA_KTX_VERSION}")
}
<repository>
<id>dv8tion</id>
<name>m2-dv8tion</name>
<url>https://m2.dv8tion.net/releases</url>
</repository>
<dependency>
<groupId>net.dv8tion</groupId>
<artifactId>JDA</artifactId>
<version>$JDA_VERSION</version>
</dependency>
<dependency>
<groupId>gay.solonovamax</groupId>
<artifactId>jda-ktx</artifactId>
<version>$JDA_KTX_VERSION</version>
</dependency>
The most useful feature of this library is the CoroutineEventManager which adds the ability to use suspending functions in your event handlers.
val jda = DefaultJDA("[your token here]") {
memberCachePolicy = MemberCachePolicy.ONLINE or MemberCachePolicy.VOICE
chunkingFilter = ChunkingFilter.NONE
compression = Compression.ZLIB
largeThreshold = 250
}
// This can only be used with the CoroutineEventManager
jda.listener<MessageReceivedEvent> {
val guild = it.guild
val channel = it.channel
val message = it.message
val content = message.contentRaw
if (content.startsWith("!profile")) {
// Send typing indicator and wait for it to arrive
channel.sendTyping().await()
val user = message.mentionedUsers.firstOrNull() ?: run {
// Try loading user through prefix loading
val matches = guild.retrieveMembersByPrefix(content.substringAfter("!profile "), 1).await()
// Take first result, or null
matches.firstOrNull()
}
if (user == null) // unknown user for name
channel.sendMessageFormat("%s, I cannot find a user for your query!", it.author).queue()
else // load profile and send it as embed
channel.sendMessageFormat("%s, here is the user profile:", it.author)
.embed(profile(user)) // custom profile embed implementation
.queue()
}
}
jda.onCommand("ban") { event ->
val user = event.getOption("user")!!.asUser
val confirm = Button.danger("${user.id}:ban", "Confirm")
event.reply("Are you sure you want to ban **${user.asTag}**?")
.addActionRow(confirm)
.setEphemeral(true)
.queue()
withTimeoutOrNull(60000) { // 1 minute timeout
val pressed = event.user.awaitButton(confirm) // await for user to click button
pressed.deferEdit().queue() // Acknowledge the button press
event.guild.ban(user, 0).queue() // the button is pressed -> execute action
} ?: event.hook.editOriginal("Timed out.").setActionRows(emptyList()).queue()
}
jda.onButton("hello") { // Button that says hello
it.reply("Hello :)").queue()
}
I've added a few suspending extension functions to various JDA components. None of these extensions require the CoroutineEventManager
!
To use await<Event>
and awaitMessage
the event manager must support either EventListener
or @SubscribeEvent
,
the ReactiveEventManager
and CoroutineEventManager
both support this.
/* Async Operations */
// Await RestAction result
suspend fun <T> RestAction<T>.await()
// Await Task result (retrieveMembersByPrefix)
suspend fun <T> Task<T>.await()
/* Event Waiter */
// Await specific event
suspend fun <T : GenericEvent> JDA.await(filter: (T) -> Boolean = { true })
// Await specific event
suspend fun <T : GenericEvent> ShardManager.await(filter: (T) -> Boolean = { true })
// Await message from specific channel (filter by user and/or filter function)
suspend fun MessageChannel.awaitMessage(author: User? = null, filter: (Message) -> Boolean = { true }): Message
/* Experimental Channel API */
// Coroutine iterators for PaginationAction
suspend fun <T, M: PaginationAction<T, M>> M.produce(scope: CoroutineScope = GlobalScope): ReceiverChannel<T>
// Flow representation for PaginationAction
suspend fun <T, M: PaginationAction<T, M>> M.asFlow(scope: CoroutineScope = GlobalScope): Flow<T>
This library implements delegate properties which can be used to safely
keep references of JDA entities such as users/channels. These delegates can be used with
the ref()
extension function:
class Foo(guild: Guild) {
val guild : Guild by guild.ref()
}
You can also use the SLF4J
delegate to initialize loggers.
object Listener : ListenerAdapter() {
private val log by SLF4J
override fun onMessageReceived(event: MessageReceivedEvent) {
log.info("[{}] {}: {}", event.channel.name, event.author.asTag, event.message.contentDispaly)
}
}
This library also provides some useful builder alternatives which can be used instead of the default MessageBuilder
and EmbedBuilder
from JDA.
You can see both builders in builders.kt.
Example
val embed = Embed(title="Hello Friend", description="Goodbye Friend")
Or the builder function style:
val embed = Embed {
title = "Hello Friend"
description = "Goodbye Friend"
field {
name = "How good is this example?"
value = "5 :star:"
inline = false
}
timestamp = Instant.now()
color = 0xFF0000
}
jda.updateCommands {
command("ban", "Ban a user") {
option<User>("user", "The user to ban", true)
option<String>("reason", "Why to ban this user")
option<Int>("duration", "For how long to ban this user") {
choice("1 day", 1)
choice("1 week", 7)
choice("1 month", 31)
}
}
command("mod", "Moderation commands") {
subcommand("ban", "Ban a user") {
option<User>("user", "The user to ban", true)
option<String>("reason", "Why to ban this user")
option<Int>("duration", "For how long to ban this user") {
choice("1 day", 1)
choice("1 week", 7)
choice("1 month", 31)
}
}
subcommand("prune", "Prune messages") {
option<Int>("amount", "The amount to delete from 2-100, default 50")
}
}
}.queue()
jda.upsertCommand("prune", "Prune messages") {
option<Int>("amount", "The amount to delete from 2-100, default 50")
}.queue()
val menu = SelectionMenu("menu:class") {
option("Frost Mage", "mage-frost", emoji=FROST_SPEC, default=true)
option("Fire Mage", "mage-fire", emoji=FIRE_SPEC)
option("Arcane Mage", "mage-arcane", emoji=ARCANE_SPEC)
}