├── .gitignore ├── README.md ├── build.gradle.kts ├── commands.md ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── src └── main ├── kotlin └── me │ └── jakejmattson │ └── bot │ ├── MainApp.kt │ ├── commands │ ├── 1 - Basics.kt │ ├── 2 - Arguments.kt │ ├── 3 - Optionals.kt │ ├── 4 - Context Apps.kt │ ├── 5 - SubCommand.kt │ ├── 6 - Text Types.kt │ ├── 7 - Text Overloads.kt │ └── 8 - Prompts.kt │ ├── conversations │ └── GreetingConversation.kt │ ├── data │ └── Configuration.kt │ ├── listeners │ └── MessageListener.kt │ ├── preconditions │ └── NamePrecondition.kt │ └── services │ ├── BotPermissions.kt │ └── MathService.kt └── resources ├── bot.properties └── simplelogger.properties /.gitignore: -------------------------------------------------------------------------------- 1 | # Configuration 2 | config/ 3 | 4 | # IntelliJ 5 | .idea/ 6 | *.iml 7 | 8 | # Gradle 9 | .gradle/ 10 | build/ 11 | gradle.properties -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | Kotlin 4 | 5 | 6 | DiscordKt 7 | 8 |
9 | 10 | Discord _jakeywakey 11 | 12 |

13 | 14 | This is an example bot project for the DiscordKt library, showing all the core features and syntax. 15 | 16 | ## Discord Setup 17 | 18 | Before you can run the example code. You need to create a bot on Discord. 19 | 20 | ### Bot Account 21 | 22 | Create a bot account in the [developers](https://discordapp.com/developers/applications/me) section of the Discord 23 | website. 24 | 25 | - Create an application 26 | - Under "General Information" 27 | - Enter an app icon and a name. 28 | - You will need the client ID when adding the bot to a server. 29 | - Under "Bot" 30 | - Create a bot. 31 | - Give it a username and app icon. 32 | - You will need the token when operating the bot. 33 | - This is a secret token, don't reveal it! 34 | - Save changes 35 | 36 | ### Add Bot 37 | 38 | - Visit the [permissions](https://discordapi.com/permissions.html) page. 39 | - Under "OAth URL Generator" enter the bot's client ID from the step above. 40 | - Click the link to add it to a server. 41 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | group = "me.jakejmattson" 2 | version = "0.24.0" 3 | description = "An example bot for DiscordKt" 4 | 5 | plugins { 6 | kotlin("jvm") version "1.9.10" 7 | kotlin("plugin.serialization") version "1.9.10" 8 | } 9 | 10 | repositories { 11 | mavenCentral() 12 | } 13 | 14 | dependencies { 15 | implementation("me.jakejmattson", "DiscordKt", version.toString()) 16 | } 17 | 18 | tasks { 19 | kotlin { 20 | jvmToolchain(11) 21 | } 22 | 23 | compileKotlin { 24 | doLast("writeProperties") {} 25 | } 26 | 27 | register("writeProperties") { 28 | property("name", project.name) 29 | property("description", project.description.toString()) 30 | property("version", version.toString()) 31 | property("url", "https://github.com/DiscordKt/ExampleBot") 32 | setOutputFile("src/main/resources/bot.properties") 33 | } 34 | } -------------------------------------------------------------------------------- /commands.md: -------------------------------------------------------------------------------- 1 | # Commands 2 | 3 | ## Key 4 | | Symbol | Meaning | 5 | |-------------|--------------------------------| 6 | | [Argument] | Argument is not required. | 7 | | /Category | This is a subcommand group. | 8 | 9 | ## /Math 10 | | Commands | Arguments | Description | 11 | |----------|---------------|-------------------------------| 12 | | Add | First, Second | Add two numbers together | 13 | | Mult | First, Second | Multiply two numbers together | 14 | | Sub | First, Second | Subtract two numbers | 15 | 16 | ## /Prompt 17 | | Commands | Arguments | Description | 18 | |----------|-----------|---------------------------------------| 19 | | Modal | | Create a prompt with a modal | 20 | | Select | | Create a prompt with a selection menu | 21 | 22 | ## Arguments 23 | | Commands | Arguments | Description | 24 | |----------|-------------------------------------|-----------------------------| 25 | | Choice | Names | Provides a choice UI | 26 | | Echo | Text | Echo the input back | 27 | | Junk | Integer, Any, User, Channel, Double | Accept a bunch of arguments | 28 | | Plus | First, Second | A simple addition command | 29 | 30 | ## Basics 31 | | Commands | Arguments | Description | 32 | |----------|-----------|-------------------------| 33 | | Embed | | Create an embed message | 34 | | Hello | | A 'Hello World' command | 35 | 36 | ## Context 37 | | Commands | Arguments | Description | 38 | |----------|-----------|-------------------------------------| 39 | | Link | Message | Get a message's jump link | 40 | | UserInfo | User | Get information for the target user | 41 | 42 | ## Conversation 43 | | Commands | Arguments | Description | 44 | |----------|-----------|--------------------------------------------| 45 | | Private | | Starts a conversation in a private channel | 46 | | Public | | Start a conversation in a public channel | 47 | | Slash | | Start a conversation from a slash command | 48 | 49 | ## Data 50 | | Commands | Arguments | Description | 51 | |----------|-----------|----------------------------------------| 52 | | Data | | Display the Data from the config file. | 53 | | SetData | Integer | Modify the Data from the config file | 54 | 55 | ## Math 56 | | Commands | Arguments | Description | 57 | |----------|---------------|-------------------------------| 58 | | Add | First, Second | Add two numbers together | 59 | | Mult | First, Second | Multiply two numbers together | 60 | | Sub | First, Second | Subtract two numbers | 61 | 62 | ## Optional 63 | | Commands | Arguments | Description | 64 | |-------------|-----------------|--------------------------------------| 65 | | Number | [Integer] | Enter any number to see the next one | 66 | | OptionalAdd | First, [Second] | Add one or two numbers together | 67 | | User | [User] | Provides the tag of a given user | 68 | 69 | ## Overload 70 | | Commands | Arguments | Description | 71 | |----------|-----------|------------------------------------------| 72 | | Overload | | This command has multiple execute blocks | 73 | | | Any | | 74 | | | Integer | | 75 | 76 | ## Permissions 77 | | Commands | Arguments | Description | 78 | |------------|-----------|-------------------------------------------| 79 | | GuildOwner | | Command requiring GUILD_OWNER permissions | 80 | | Staff | | Command requiring STAFF permissions | 81 | 82 | ## Preconditions 83 | | Commands | Arguments | Description | 84 | |--------------|-----------|---------------------------------------| 85 | | Precondition | | Show a command failing a precondition | 86 | 87 | ## Text 88 | | Commands | Arguments | Description | 89 | |----------|-----------|-------------------------------------------| 90 | | Dm | | This command can only be used in a DM | 91 | | Global | | This command can be used in a guild or DM | 92 | | Guild | | This command can only be used in a guild | 93 | | Menu | | Create a menu message | 94 | 95 | ## Utility 96 | | Commands | Arguments | Description | 97 | |----------|-----------|-------------------------| 98 | | Help | [Command] | Display a help menu. | 99 | | info | | Bot info for ExampleBot | 100 | 101 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DiscordKt/ExampleBot/64dbf68e335433d009e248fa92a8f016df31702d/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | # This is normally unused 84 | # shellcheck disable=SC2034 85 | APP_BASE_NAME=${0##*/} 86 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) 87 | APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit 88 | 89 | # Use the maximum available, or set MAX_FD != -1 to use that value. 90 | MAX_FD=maximum 91 | 92 | warn () { 93 | echo "$*" 94 | } >&2 95 | 96 | die () { 97 | echo 98 | echo "$*" 99 | echo 100 | exit 1 101 | } >&2 102 | 103 | # OS specific support (must be 'true' or 'false'). 104 | cygwin=false 105 | msys=false 106 | darwin=false 107 | nonstop=false 108 | case "$( uname )" in #( 109 | CYGWIN* ) cygwin=true ;; #( 110 | Darwin* ) darwin=true ;; #( 111 | MSYS* | MINGW* ) msys=true ;; #( 112 | NONSTOP* ) nonstop=true ;; 113 | esac 114 | 115 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 116 | 117 | 118 | # Determine the Java command to use to start the JVM. 119 | if [ -n "$JAVA_HOME" ] ; then 120 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 121 | # IBM's JDK on AIX uses strange locations for the executables 122 | JAVACMD=$JAVA_HOME/jre/sh/java 123 | else 124 | JAVACMD=$JAVA_HOME/bin/java 125 | fi 126 | if [ ! -x "$JAVACMD" ] ; then 127 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 128 | 129 | Please set the JAVA_HOME variable in your environment to match the 130 | location of your Java installation." 131 | fi 132 | else 133 | JAVACMD=java 134 | if ! command -v java >/dev/null 2>&1 135 | then 136 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | fi 142 | 143 | # Increase the maximum file descriptors if we can. 144 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 145 | case $MAX_FD in #( 146 | max*) 147 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 148 | # shellcheck disable=SC2039,SC3045 149 | MAX_FD=$( ulimit -H -n ) || 150 | warn "Could not query maximum file descriptor limit" 151 | esac 152 | case $MAX_FD in #( 153 | '' | soft) :;; #( 154 | *) 155 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 156 | # shellcheck disable=SC2039,SC3045 157 | ulimit -n "$MAX_FD" || 158 | warn "Could not set maximum file descriptor limit to $MAX_FD" 159 | esac 160 | fi 161 | 162 | # Collect all arguments for the java command, stacking in reverse order: 163 | # * args from the command line 164 | # * the main class name 165 | # * -classpath 166 | # * -D...appname settings 167 | # * --module-path (only if needed) 168 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 169 | 170 | # For Cygwin or MSYS, switch paths to Windows format before running java 171 | if "$cygwin" || "$msys" ; then 172 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 173 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 174 | 175 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 176 | 177 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 178 | for arg do 179 | if 180 | case $arg in #( 181 | -*) false ;; # don't mess with options #( 182 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 183 | [ -e "$t" ] ;; #( 184 | *) false ;; 185 | esac 186 | then 187 | arg=$( cygpath --path --ignore --mixed "$arg" ) 188 | fi 189 | # Roll the args list around exactly as many times as the number of 190 | # args, so each arg winds up back in the position where it started, but 191 | # possibly modified. 192 | # 193 | # NB: a `for` loop captures its iteration list before it begins, so 194 | # changing the positional parameters here affects neither the number of 195 | # iterations, nor the values presented in `arg`. 196 | shift # remove old arg 197 | set -- "$@" "$arg" # push replacement arg 198 | done 199 | fi 200 | 201 | 202 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 203 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 204 | 205 | # Collect all arguments for the java command: 206 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, 207 | # and any embedded shellness will be escaped. 208 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be 209 | # treated as '${Hostname}' itself on the command line. 210 | 211 | set -- \ 212 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 213 | -classpath "$CLASSPATH" \ 214 | org.gradle.wrapper.GradleWrapperMain \ 215 | "$@" 216 | 217 | # Stop when "xargs" is not available. 218 | if ! command -v xargs >/dev/null 2>&1 219 | then 220 | die "xargs is not available" 221 | fi 222 | 223 | # Use "xargs" to parse quoted args. 224 | # 225 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 226 | # 227 | # In Bash we could simply go: 228 | # 229 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 230 | # set -- "${ARGS[@]}" "$@" 231 | # 232 | # but POSIX shell has neither arrays nor command substitution, so instead we 233 | # post-process each arg (as a line of input to sed) to backslash-escape any 234 | # character that might be a shell metacharacter, then use eval to reverse 235 | # that process (while maintaining the separation between arguments), and wrap 236 | # the whole thing up as a single "set" statement. 237 | # 238 | # This will of course break if any of these variables contains a newline or 239 | # an unmatched quote. 240 | # 241 | 242 | eval "set -- $( 243 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 244 | xargs -n1 | 245 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 246 | tr '\n' ' ' 247 | )" '"$@"' 248 | 249 | exec "$JAVACMD" "$@" 250 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 48 | echo. 49 | echo Please set the JAVA_HOME variable in your environment to match the 50 | echo location of your Java installation. 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 62 | echo. 63 | echo Please set the JAVA_HOME variable in your environment to match the 64 | echo location of your Java installation. 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /src/main/kotlin/me/jakejmattson/bot/MainApp.kt: -------------------------------------------------------------------------------- 1 | package me.jakejmattson.bot 2 | 3 | import dev.kord.common.annotation.KordPreview 4 | import dev.kord.core.behavior.channel.createEmbed 5 | import dev.kord.core.entity.channel.TextChannel 6 | import dev.kord.core.event.channel.TypingStartEvent 7 | import dev.kord.core.event.message.MessageCreateEvent 8 | import dev.kord.gateway.Intents 9 | import dev.kord.gateway.NON_PRIVILEGED 10 | import dev.kord.x.emoji.Emojis 11 | import kotlinx.coroutines.flow.toList 12 | import me.jakejmattson.bot.data.Configuration 13 | import me.jakejmattson.bot.services.BotPermissions 14 | import me.jakejmattson.discordkt.dsl.CommandException 15 | import me.jakejmattson.discordkt.dsl.ListenerException 16 | import me.jakejmattson.discordkt.dsl.bot 17 | import me.jakejmattson.discordkt.locale.Language 18 | import me.jakejmattson.discordkt.util.intentsOf 19 | import me.jakejmattson.discordkt.util.toSnowflake 20 | import java.awt.Color 21 | 22 | @KordPreview 23 | suspend fun main(args: Array) { 24 | //Get the bot token from the command line (or your preferred way). 25 | //Start the bot and set configuration options. 26 | bot(args.firstOrNull()) { 27 | //See the Data example 28 | val configuration = data("config/config.json") { Configuration() } 29 | 30 | //Dynamically determine the prefix used for commands. 31 | prefix { 32 | configuration.prefix 33 | } 34 | 35 | //Simple configuration options 36 | configure { 37 | //Allow a mention to be used in front of commands ('@Bot help'). 38 | mentionAsPrefix = true 39 | 40 | //Whether to show registered entity information on startup. 41 | logStartup = true 42 | 43 | //Whether to generate documentation for registered commands. 44 | documentCommands = true 45 | 46 | //Whether to recommend commands when an invalid one is invoked. 47 | recommendCommands = true 48 | 49 | //Allow users to search for a command by typing 'search '. 50 | searchCommands = true 51 | 52 | //Remove a command invocation message after the command is executed. 53 | deleteInvocation = true 54 | 55 | //Allow slash commands to be invoked as text commands. 56 | dualRegistry = true 57 | 58 | //An emoji added when a command is invoked (use 'null' to disable this). 59 | commandReaction = Emojis.eyes 60 | 61 | //A color constant for your bot - typically used in embeds. 62 | theme = Color(0x00BFFF) 63 | 64 | //Configure the Discord Gateway intents for your bot. 65 | intents = Intents.NON_PRIVILEGED + intentsOf() 66 | 67 | //Set the default permission required for slash commands. 68 | defaultPermissions = BotPermissions.EVERYONE 69 | } 70 | 71 | onException { 72 | if (exception is IllegalArgumentException) 73 | return@onException 74 | 75 | when (this) { 76 | is CommandException -> println("Exception '${exception::class.simpleName}' in command ${event.command?.name}") 77 | is ListenerException -> println("Exception '${exception::class.simpleName}' in listener ${event::class.simpleName}") 78 | } 79 | } 80 | 81 | //The Discord presence shown on your bot. 82 | presence { 83 | playing("DiscordKt Example") 84 | } 85 | 86 | //This is run once the bot has finished setup and logged in. 87 | onStart { 88 | val guilds = kord.guilds.toList() 89 | println("Guilds: ${guilds.joinToString { it.name }}") 90 | } 91 | 92 | //Configure the locale for this bot. 93 | localeOf(Language.EN.locale) { 94 | helpName = "Help" 95 | helpCategory = "Utility" 96 | commandRecommendation = "Recommendation: {0}" 97 | } 98 | } 99 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/jakejmattson/bot/commands/1 - Basics.kt: -------------------------------------------------------------------------------- 1 | package me.jakejmattson.bot.commands 2 | 3 | import me.jakejmattson.discordkt.commands.commands 4 | 5 | //To register commands, use the 'commands' builder function. 6 | //This creates some new commands in a category called 'Basic'. 7 | fun basics() = commands("Basics") { 8 | //This block creates a new command called 'Hello'. 9 | //Descriptions are used for docs and help menus. 10 | slash("Hello", "A 'Hello World' command") { 11 | execute { //The 'execute' block is what code will be run when your command is invoked. 12 | //The 'respond' command can be used to send a message back to the user who ran the command. 13 | respond("Hello World!") 14 | } 15 | } 16 | 17 | slash("Embed", "Create an embed message") { 18 | execute { 19 | //You can also respond with a Discord embed. 20 | respond { 21 | title = "This is an embed" 22 | } 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/jakejmattson/bot/commands/2 - Arguments.kt: -------------------------------------------------------------------------------- 1 | package me.jakejmattson.bot.commands 2 | 3 | import me.jakejmattson.discordkt.arguments.* 4 | import me.jakejmattson.discordkt.commands.commands 5 | 6 | //Most of the time, you will want your commands to accept input. 7 | //This can be accomplished with the different ArgumentTypes. 8 | fun arguments() = commands("Arguments") { 9 | slash("Echo", "Echo the input back") { 10 | execute(EveryArg) { 11 | //All user input will be contained in 'args' with the correct type information. 12 | //If the execute block is run, you can guarantee that the data is there. 13 | val input = args.first 14 | respond(input) 15 | } 16 | } 17 | 18 | slash("Plus", "A simple addition command") { 19 | execute(IntegerArg("First"), IntegerArg("Second")) { 20 | val (first, second) = args 21 | respond("$first + $second = ${first + second}") 22 | } 23 | } 24 | 25 | slash("Choice", "Provides a choice UI") { 26 | execute(ChoiceArg("Names", "Names of cool people", "Jake", "David", "Elliott", "Moe")) { 27 | respond("You chose ${args.first}") 28 | } 29 | } 30 | 31 | slash("Junk", "Accept a bunch of arguments") { 32 | //You can accept as many arguments as you want. 33 | execute(IntegerArg, AnyArg, UserArg, ChannelArg, DoubleArg) { 34 | val (int, word, user, channel, double) = args 35 | respond( 36 | """ 37 | ``` 38 | Integer: $int 39 | One Word: $word 40 | User Tag: ${user.tag} 41 | Channel: ${channel.name} 42 | Decimal: $double 43 | ``` 44 | """.trimIndent() 45 | ) 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/jakejmattson/bot/commands/3 - Optionals.kt: -------------------------------------------------------------------------------- 1 | package me.jakejmattson.bot.commands 2 | 3 | import me.jakejmattson.discordkt.arguments.IntegerArg 4 | import me.jakejmattson.discordkt.arguments.UserArg 5 | import me.jakejmattson.discordkt.commands.commands 6 | 7 | //Arguments can also be given optionally, and fall back to a default value if not provided by the user. 8 | fun optionals() = commands("Optional") { 9 | slash("Number", "Enter any number to see the next one") { 10 | //This command accepts either 0 or 1 arguments, and defaults to '0' if none are provided. 11 | execute(IntegerArg.optional(0)) { 12 | respond("Next: ${args.first + 1}") 13 | } 14 | } 15 | 16 | slash("User", "Provides the tag of a given user") { 17 | //This optional block exposes the command event for access to discord entities. 18 | execute(UserArg.optional { it.author }) { 19 | respond(args.first.tag) 20 | } 21 | } 22 | 23 | slash("OptionalAdd", "Add one or two numbers together") { 24 | //Optional arguments must come after required ones. 25 | execute(IntegerArg("First"), IntegerArg("Second").optional(0)) { 26 | val (first, second) = args 27 | respond("$first + $second = ${first + second}") 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/jakejmattson/bot/commands/4 - Context Apps.kt: -------------------------------------------------------------------------------- 1 | package me.jakejmattson.bot.commands 2 | 3 | import me.jakejmattson.discordkt.commands.commands 4 | import me.jakejmattson.discordkt.util.descriptor 5 | import me.jakejmattson.discordkt.util.jumpLink 6 | 7 | fun context() = commands("Context") { 8 | /** 9 | * Creates a right-click message app. 10 | */ 11 | message(displayText = "Get Message Link", slashName = "Link", description = "Get a message's jump link") { 12 | respond(arg.jumpLink() ?: "") 13 | } 14 | 15 | /** 16 | * Creates a right-click user app. 17 | */ 18 | user(displayText = "Show User Info", slashName = "UserInfo", description = "Get information for the target user") { 19 | respond(arg.descriptor()) 20 | } 21 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/jakejmattson/bot/commands/5 - SubCommand.kt: -------------------------------------------------------------------------------- 1 | package me.jakejmattson.bot.commands 2 | 3 | import me.jakejmattson.discordkt.arguments.IntegerArg 4 | import me.jakejmattson.discordkt.commands.subcommand 5 | 6 | fun mathSubcommand() = subcommand("Math") { 7 | sub("Add", "Add two numbers together") { 8 | execute(IntegerArg("First"), IntegerArg("Second")) { 9 | respond(args.first + args.second) 10 | } 11 | } 12 | 13 | sub("Sub", "Subtract two numbers") { 14 | execute(IntegerArg("First"), IntegerArg("Second")) { 15 | respond(args.first - args.second) 16 | } 17 | } 18 | 19 | sub("Mult", "Multiply two numbers together") { 20 | execute(IntegerArg("First"), IntegerArg("Second")) { 21 | respond(args.first * args.second) 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/jakejmattson/bot/commands/6 - Text Types.kt: -------------------------------------------------------------------------------- 1 | package me.jakejmattson.bot.commands 2 | 3 | import dev.kord.common.kColor 4 | import dev.kord.x.emoji.Emojis 5 | import me.jakejmattson.discordkt.commands.commands 6 | import java.awt.Color 7 | 8 | //There are different builders for different types of commands. 9 | //This allows more type-safe access to data, such as channels. 10 | fun textCommands() = commands("Text") { 11 | text("Guild") { 12 | description = "This command can only be used in a guild" 13 | execute { 14 | //This event has a TextChannel with a non-nullable Guild. 15 | val channel = channel 16 | respond("Hello ${channel.name} (${guild.name})") 17 | } 18 | } 19 | 20 | globalText("Global") { 21 | description = "This command can be used in a guild or DM" 22 | execute { 23 | //This event has a generic MessageChannel and a nullable Guild. 24 | val guildName = guild?.name ?: "DM to ${author.tag}" 25 | respond("Hello $guildName") 26 | } 27 | } 28 | 29 | dmText("Dm") { 30 | description = "This command can only be used in a DM" 31 | execute { 32 | //This event has a DmChannel and no Guild data. 33 | val channel = channel 34 | respond("DM to ${author.tag}") 35 | } 36 | } 37 | 38 | text("Menu") { 39 | description = "Create a menu message" 40 | execute { 41 | //You can also create embeds with multiple pages and custom buttons. 42 | respondMenu { 43 | page { title = "Page 1" } 44 | page { title = "Page 2" } 45 | 46 | //Creates a new button row 47 | buttons { 48 | //Exposes the menu for navigation functions. 49 | button("Left", Emojis.arrowLeft) { previousPage() } 50 | button("Right", Emojis.arrowRight) { nextPage() } 51 | 52 | //Exposes the current embed page to be edited. 53 | editButton("Rainbow", Emojis.rainbow) { color = genRandomColor() } 54 | 55 | //Opens the specified link in the browser. 56 | linkButton("Source", Emojis.pageFacingUp, "https://github.com/DiscordKt/ExampleBot") 57 | } 58 | } 59 | } 60 | } 61 | } 62 | 63 | private fun genRandomColor() = Color(genRandomRGB(), genRandomRGB(), genRandomRGB()).kColor 64 | private fun genRandomRGB() = (0..255).random() -------------------------------------------------------------------------------- /src/main/kotlin/me/jakejmattson/bot/commands/7 - Text Overloads.kt: -------------------------------------------------------------------------------- 1 | package me.jakejmattson.bot.commands 2 | 3 | import me.jakejmattson.discordkt.arguments.AnyArg 4 | import me.jakejmattson.discordkt.arguments.IntegerArg 5 | import me.jakejmattson.discordkt.commands.commands 6 | 7 | fun overload() = commands("Overload") { 8 | //Commands can be overloaded - have multiple execute blocks. 9 | //This allows you to process different args differently. 10 | text("Overload") { 11 | description = "This command has multiple execute blocks" 12 | execute { 13 | respond("I took no args") 14 | } 15 | 16 | execute(IntegerArg) { 17 | respond("I took an int: ${args.first}") 18 | } 19 | 20 | execute(AnyArg) { 21 | respond("I took a word: ${args.first}") 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/jakejmattson/bot/commands/8 - Prompts.kt: -------------------------------------------------------------------------------- 1 | package me.jakejmattson.bot.commands 2 | 3 | import dev.kord.common.entity.TextInputStyle 4 | import dev.kord.core.behavior.interaction.response.respond 5 | import dev.kord.x.emoji.Emojis 6 | import me.jakejmattson.discordkt.commands.subcommand 7 | import me.jakejmattson.discordkt.prompts.promptModal 8 | import me.jakejmattson.discordkt.prompts.promptSelect 9 | import me.jakejmattson.discordkt.util.toPartialEmoji 10 | 11 | fun promptCommands() = subcommand("Prompt") { 12 | sub("Modal", "Create a prompt with a modal") { 13 | execute { 14 | val (responseInteraction, inputs) = promptModal(interaction!!, "Enter Information") { 15 | input("Name", TextInputStyle.Short) 16 | input("Age", TextInputStyle.Short) 17 | } 18 | 19 | val (name, age) = inputs 20 | 21 | responseInteraction.respond { 22 | content = "Hello $name! $age is a great age" 23 | } 24 | } 25 | } 26 | 27 | sub("Select", "Create a prompt with a selection menu") { 28 | execute { 29 | val (response, inputs) = promptSelect(interaction!!) { 30 | this.selectionCount = 1..4 31 | 32 | content { 33 | title = "Selection" 34 | description = "Pick some good animals" 35 | } 36 | 37 | option("Cat", description = "Meow", emoji = Emojis.cat.toPartialEmoji()) 38 | option("Dog", description = "Bark", emoji = Emojis.dog.toPartialEmoji()) 39 | option("Fish", description = "Glub", emoji = Emojis.fish.toPartialEmoji()) 40 | option("Bird", description = "Squawk", emoji = Emojis.bird.toPartialEmoji()) 41 | } 42 | 43 | response.respond { 44 | content = "Chosen: $inputs" 45 | } 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/jakejmattson/bot/conversations/GreetingConversation.kt: -------------------------------------------------------------------------------- 1 | package me.jakejmattson.bot.conversations 2 | 3 | import dev.kord.common.Color 4 | import dev.kord.x.emoji.Emojis 5 | import me.jakejmattson.discordkt.arguments.AnyArg 6 | import me.jakejmattson.discordkt.arguments.IntegerArg 7 | import me.jakejmattson.discordkt.commands.commands 8 | import me.jakejmattson.discordkt.conversations.conversation 9 | import me.jakejmattson.discordkt.util.toPartialEmoji 10 | 11 | fun greetingConversation() = conversation("exit", 30) { 12 | val name = prompt(AnyArg, "What is your name?") 13 | 14 | val age = prompt(IntegerArg) { 15 | title = "How old are you?" 16 | } 17 | 18 | val response = promptButton { 19 | embed { 20 | title = "Do you like DiscordKt?" 21 | color = Color(0x00bfff) 22 | } 23 | 24 | buttons { 25 | button("Yes", Emojis.whiteCheckMark, "Glad you like it.") 26 | button("No", Emojis.x, "You should let me know how to fix the lib.") 27 | } 28 | } 29 | 30 | val selection = promptSelect { 31 | this.selectionCount = 1..1 32 | 33 | content { 34 | title = "Selection" 35 | description = "What's your favorite letter?" 36 | } 37 | 38 | option("A", description = "The first letter", emoji = Emojis.regionalIndicatorA.toPartialEmoji()) 39 | option("B", description = "The second letter", emoji = Emojis.regionalIndicatorB.toPartialEmoji()) 40 | option("C", description = "The third letter", emoji = Emojis.regionalIndicatorC.toPartialEmoji()) 41 | } 42 | 43 | respond("Nice to meet you $name ($age)! $response ${selection.first()} is my favorite letter too.") 44 | } 45 | 46 | fun conversationCommands() = commands("Conversation") { 47 | text("Public") { 48 | description = "Start a conversation in a public channel" 49 | execute { 50 | val result = greetingConversation().startPublicly(discord, author, channel) 51 | println(result) 52 | } 53 | } 54 | 55 | text("Private") { 56 | description = "Starts a conversation in a private channel" 57 | execute { 58 | val result = greetingConversation().startPrivately(discord, author) 59 | println(result) 60 | } 61 | } 62 | 63 | slash("Slash", "Start a conversation from a slash command") { 64 | execute { 65 | val result = greetingConversation().startSlashResponse(discord, author, this) 66 | println(result) 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/jakejmattson/bot/data/Configuration.kt: -------------------------------------------------------------------------------- 1 | package me.jakejmattson.bot.data 2 | 3 | import dev.kord.common.entity.Snowflake 4 | import kotlinx.serialization.Serializable 5 | import me.jakejmattson.discordkt.arguments.IntegerArg 6 | import me.jakejmattson.discordkt.commands.commands 7 | import me.jakejmattson.discordkt.dsl.Data 8 | import me.jakejmattson.discordkt.dsl.edit 9 | import me.jakejmattson.discordkt.util.pfpUrl 10 | 11 | //A Data class is a serializable set of data stored in a file as JSON. 12 | //These classes can be loaded in the main bot function and saved once modified. 13 | //This is frequently used for configurations, but can be used for any persistent data. 14 | @Serializable 15 | data class Configuration( 16 | val botOwner: Snowflake = Snowflake(254786431656919051), 17 | val prefix: String = "ex!", 18 | var favoriteNumber: Int = 3 19 | ) : Data() 20 | 21 | fun dataCommands(configuration: Configuration) = commands("Data") { 22 | slash("Data", "Display the Data from the config file.") { 23 | execute { 24 | val owner = discord.kord.getUser(configuration.botOwner)!! 25 | 26 | respond { 27 | title = owner.username 28 | description = "My favorite number is ${configuration.favoriteNumber}" 29 | 30 | thumbnail { 31 | url = owner.pfpUrl 32 | } 33 | } 34 | } 35 | } 36 | 37 | slash("SetData", "Modify the Data from the config file") { 38 | execute(IntegerArg) { 39 | val input = args.first 40 | configuration.edit { favoriteNumber = input } 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/jakejmattson/bot/listeners/MessageListener.kt: -------------------------------------------------------------------------------- 1 | package me.jakejmattson.bot.listeners 2 | 3 | import dev.kord.core.event.message.MessageCreateEvent 4 | import me.jakejmattson.discordkt.dsl.listeners 5 | 6 | //Create a block of listeners. 7 | fun testListeners() = listeners { 8 | 9 | //You can use `on` to listen for a Discord event. 10 | on { 11 | //Ignore the message if it was sent by a bot. 12 | require(message.author?.isBot == false) 13 | 14 | //Every time a message is sent, print the content. 15 | println(message.content) 16 | } 17 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/jakejmattson/bot/preconditions/NamePrecondition.kt: -------------------------------------------------------------------------------- 1 | package me.jakejmattson.bot.preconditions 2 | 3 | import me.jakejmattson.discordkt.commands.commands 4 | import me.jakejmattson.discordkt.dsl.precondition 5 | 6 | //Preconditions are a way to make sure that a command is allowed to be run. 7 | fun namePrecondition() = precondition { 8 | val commandNames = command?.names ?: return@precondition 9 | 10 | //Preconditions can fail with a message that is sent as a response. 11 | //In order for a command to run, every precondition must pass. 12 | if ("Precondition" in commandNames) 13 | fail("Precondition Failed.") 14 | } 15 | 16 | fun preconditions() = commands("Preconditions") { 17 | slash("Precondition", "Show a command failing a precondition") { 18 | execute { 19 | //This does not run, since the above precondition fails. 20 | respond("Precondition passed.") 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/jakejmattson/bot/services/BotPermissions.kt: -------------------------------------------------------------------------------- 1 | package me.jakejmattson.bot.services 2 | 3 | import dev.kord.common.entity.Permission 4 | import dev.kord.common.entity.Permissions 5 | import me.jakejmattson.discordkt.commands.commands 6 | 7 | //This enum defines a hierarchy of permissions - commands are marked as requiring a certain permission level 8 | //If a user meets or exceeds that permission level, they can execute the command 9 | //This enum must be registered in the configure block along with a default command permission 10 | object BotPermissions { 11 | val GUILD_OWNER = Permissions(Permission.ManageGuild) 12 | val STAFF = Permissions(Permission.ManageMessages) 13 | val EVERYONE = Permissions(Permission.UseApplicationCommands) 14 | } 15 | 16 | //The commands builder can accept a required permission that will be applied to all commands in this category 17 | fun permissionCommands() = commands("Permissions", BotPermissions.GUILD_OWNER) { 18 | slash("GuildOwner", "Command requiring GUILD_OWNER permissions") { 19 | execute { 20 | respond("Hello guild owner!") 21 | } 22 | } 23 | 24 | //Commands within a category can override the permission level of that category 25 | slash("Staff", "Command requiring STAFF permissions", BotPermissions.STAFF) { 26 | execute { 27 | respond("Hello staff member!") 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/jakejmattson/bot/services/MathService.kt: -------------------------------------------------------------------------------- 1 | package me.jakejmattson.bot.services 2 | 3 | import me.jakejmattson.discordkt.annotations.Service 4 | import me.jakejmattson.discordkt.arguments.IntegerArg 5 | import me.jakejmattson.discordkt.commands.commands 6 | 7 | //A service defines logic that can be used somewhere else. 8 | //This prevents duplicating code and cluttering commands. 9 | @Service 10 | class MathService { 11 | fun add(a: Int, b: Int) = a + b 12 | fun sub(a: Int, b: Int) = a - b 13 | fun mult(a: Int, b: Int) = a * b 14 | } 15 | 16 | //To use Services somewhere, just request them as parameters. 17 | fun mathCommands(mathService: MathService) = commands("Math") { 18 | slash("Add", "Add two numbers together") { 19 | execute(IntegerArg("First"), IntegerArg("Second")) { 20 | val (first, second) = args 21 | val result = mathService.add(first, second) 22 | respond(result) 23 | } 24 | } 25 | 26 | slash("Sub", "Subtract two numbers") { 27 | execute(IntegerArg("First"), IntegerArg("Second")) { 28 | val (first, second) = args 29 | val result = mathService.sub(first, second) 30 | respond(result) 31 | } 32 | } 33 | 34 | slash("Mult", "Multiply two numbers together") { 35 | execute(IntegerArg("First"), IntegerArg("Second")) { 36 | val (first, second) = args 37 | val result = mathService.mult(first, second) 38 | respond(result) 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /src/main/resources/bot.properties: -------------------------------------------------------------------------------- 1 | description=An example bot for DiscordKt 2 | name=ExampleBot 3 | url=https\://github.com/DiscordKt/ExampleBot 4 | version=0.24.0 5 | -------------------------------------------------------------------------------- /src/main/resources/simplelogger.properties: -------------------------------------------------------------------------------- 1 | org.slf4j.simpleLogger.defaultLogLevel=error 2 | org.slf4j.simpleLogger.showDateTime=true --------------------------------------------------------------------------------