diff --git a/.gitignore b/.gitignore index cec0a44..1c20f2b 100644 --- a/.gitignore +++ b/.gitignore @@ -190,4 +190,4 @@ gradle-app.setting # End of https://www.toptal.com/developers/gitignore/api/intellij+all,java,maven,gradle,kotlin,git # Example files -examples/example-jda5/bot.properties +examples/example-*/bot.properties diff --git a/cloud-kord/build.gradle.kts b/cloud-kord/build.gradle.kts new file mode 100644 index 0000000..5c7a6fa --- /dev/null +++ b/cloud-kord/build.gradle.kts @@ -0,0 +1,14 @@ +plugins { + id("cloud-discord.kotlin-conventions") + id("cloud-discord.publishing-conventions") +} + +dependencies { + api(projects.cloudDiscordCommon) + api(libs.bundles.coroutines) + + implementation(libs.cloud.annotations) + implementation(libs.kord) + + testImplementation(libs.mockito.kotlin) +} diff --git a/cloud-kord/src/main/kotlin/org/incendo/cloud/discord/kord/KordCommandManager.kt b/cloud-kord/src/main/kotlin/org/incendo/cloud/discord/kord/KordCommandManager.kt new file mode 100644 index 0000000..8df95ae --- /dev/null +++ b/cloud-kord/src/main/kotlin/org/incendo/cloud/discord/kord/KordCommandManager.kt @@ -0,0 +1,49 @@ +// +// MIT License +// +// Copyright (c) 2024 Incendo +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +package org.incendo.cloud.discord.kord + +import cloud.commandframework.CommandManager +import cloud.commandframework.execution.ExecutionCoordinator +import cloud.commandframework.internal.CommandRegistrationHandler +import org.apiguardian.api.API + +/** + * Command manager for Kord. + * + * @param C command sender type + * @since 1.0.0 + */ +@API(status = API.Status.STABLE, since = "1.0.0") +public class KordCommandManager(executionCoordinator: ExecutionCoordinator) : CommandManager( + executionCoordinator, + CommandRegistrationHandler.nullCommandRegistrationHandler() +) { + + /** + * Predicate used to evaluate sender permissions. + */ + public var permissionPredicate: (C, String) -> Boolean = { _, _ -> true } + + override fun hasPermission(sender: C, permission: String): Boolean = permissionPredicate(sender, permission) +} diff --git a/examples/example-kord/build.gradle.kts b/examples/example-kord/build.gradle.kts new file mode 100644 index 0000000..d07b5a5 --- /dev/null +++ b/examples/example-kord/build.gradle.kts @@ -0,0 +1,25 @@ +plugins { + id("cloud-discord.kotlin-conventions") + application +} + +indra { + javaVersions { + minimumToolchain(17) + target(17) + testWith().set(setOf(17)) + } +} + +dependencies { + implementation(projects.cloudKord) + implementation(libs.cloud.annotations) + implementation(libs.kord) + implementation(libs.kotlin.logging) + implementation(libs.logback.core) + implementation(libs.logback.classic) +} + +application { + mainClass = "org.incendo.cloud.discord.kord.example.ExampleBotKt" +} diff --git a/examples/example-kord/src/main/kotlin/org/incendo/cloud/discord/kord/example/BotConfiguration.kt b/examples/example-kord/src/main/kotlin/org/incendo/cloud/discord/kord/example/BotConfiguration.kt new file mode 100644 index 0000000..c6f561c --- /dev/null +++ b/examples/example-kord/src/main/kotlin/org/incendo/cloud/discord/kord/example/BotConfiguration.kt @@ -0,0 +1,49 @@ +// +// MIT License +// +// Copyright (c) 2024 Incendo +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +package org.incendo.cloud.discord.kord.example + +import java.io.File +import java.io.FileReader +import java.util.Properties + +/** + * Configuration for the example bot. + */ +public interface BotConfiguration { + + /** + * The bot token. + */ + public val token: String +} + +internal class PropertiesBotConfiguration private constructor(private val properties: Properties) : BotConfiguration { + + internal constructor(file: File) : this(Properties()) { + FileReader(file).use(properties::load) + } + + override val token: String + get() = properties.getProperty("token") +} diff --git a/examples/example-kord/src/main/kotlin/org/incendo/cloud/discord/kord/example/ExampleBot.kt b/examples/example-kord/src/main/kotlin/org/incendo/cloud/discord/kord/example/ExampleBot.kt new file mode 100644 index 0000000..72620ed --- /dev/null +++ b/examples/example-kord/src/main/kotlin/org/incendo/cloud/discord/kord/example/ExampleBot.kt @@ -0,0 +1,57 @@ +// +// MIT License +// +// Copyright (c) 2024 Incendo +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +package org.incendo.cloud.discord.kord.example + +import cloud.commandframework.execution.ExecutionCoordinator +import dev.kord.core.Kord +import io.github.oshai.kotlinlogging.KotlinLogging +import org.incendo.cloud.discord.kord.KordCommandManager +import java.io.File + +private val logger = KotlinLogging.logger {} + +/** + * Example kord bot. + */ +public class ExampleBot(public val configuration: BotConfiguration) { + + /** + * Starts the bot. + */ + public suspend fun start() { + logger.info { "Starting the example bot..." } + val commandManager = KordCommandManager(ExecutionCoordinator.simpleCoordinator()) + + logger.info { "Logging into Kord..." } + val kord = Kord(configuration.token) + kord.login() + } +} + +/** + * Main method. + */ +public suspend fun main() { + ExampleBot(PropertiesBotConfiguration(File("./bot.properties"))).start() +} diff --git a/examples/example-kord/src/main/resources/logback.xml b/examples/example-kord/src/main/resources/logback.xml new file mode 100644 index 0000000..78f3e89 --- /dev/null +++ b/examples/example-kord/src/main/resources/logback.xml @@ -0,0 +1,10 @@ + + + + [%d{HH:mm:ss}][%-5level][%-30logger{0}]: %msg%n + + + + + + diff --git a/gradle/build-logic/build.gradle.kts b/gradle/build-logic/build.gradle.kts index 1ad12cc..f7a5a35 100644 --- a/gradle/build-logic/build.gradle.kts +++ b/gradle/build-logic/build.gradle.kts @@ -10,6 +10,7 @@ repositories { dependencies { implementation(libs.cloud.build.logic) implementation(libs.gradleKotlinJvm) + implementation(libs.gradleDokka) implementation(files(libs.javaClass.superclass.protectionDomain.codeSource.location)) } diff --git a/gradle/build-logic/src/main/kotlin/cloud-discord.kotlin-conventions.gradle.kts b/gradle/build-logic/src/main/kotlin/cloud-discord.kotlin-conventions.gradle.kts new file mode 100644 index 0000000..f268084 --- /dev/null +++ b/gradle/build-logic/src/main/kotlin/cloud-discord.kotlin-conventions.gradle.kts @@ -0,0 +1,32 @@ +import gradle.kotlin.dsl.accessors._4170a67d0be8a515d9becde6b6ee87f3.api +import org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension + +plugins { + id("cloud-discord.base-conventions") + id("org.jetbrains.kotlin.jvm") + id("org.jetbrains.dokka") +} + +configure { + explicitApi() + jvmToolchain { + languageVersion.set(JavaLanguageVersion.of(17)) + } + coreLibrariesVersion = libs.versions.kotlin.get() + target { + compilations.configureEach { + kotlinOptions { + jvmTarget = "17" + languageVersion = libs.versions.kotlin.get().split(".").take(2).joinToString(".") + } + } + } + + dependencies { + api(kotlin("stdlib-jdk8")) + } + + tasks.named("javadocJar", AbstractArchiveTask::class) { + from(tasks.named("dokkaHtml")) + } +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 51b3e3e..19e5b0f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -7,7 +7,11 @@ cloud-buildLogic-rootProject-spotless = { id = "org.incendo.cloud-build-logic.sp cloud-build-logic = "0.0.3" ktlint = "1.0.1" checkstyle = "10.12.5" + +# Kotlin kotlin = "1.9.22" +dokka = "1.9.10" +coroutines = "1.7.3" # Cloud cloud = "2.0.0-SNAPSHOT" @@ -18,16 +22,20 @@ javacord = "3.8.0" jda = "4.4.1_353" jda5 = "5.0.0-beta.19" logback = "1.4.14" +kord = "0.13.0" +kotlinLogging = "6.0.3" # Test jupiterEngine = "5.10.1" mockitoCore = "4.11.0" mockitoJupiter = "4.11.0" +mockitoKotlin = "4.1.0" truth = "1.2.0" [libraries] cloud-build-logic = { module = "org.incendo:cloud-build-logic", version.ref = "cloud-build-logic" } gradleKotlinJvm = { group = "org.jetbrains.kotlin.jvm", name = "org.jetbrains.kotlin.jvm.gradle.plugin", version.ref = "kotlin" } +gradleDokka = { group = "org.jetbrains.dokka", name = "dokka-gradle-plugin", version.ref = "dokka" } # Cloud cloud-core = { group = "cloud.commandframework", name = "cloud-core", version.ref = "cloud" } @@ -40,14 +48,21 @@ jda = { group = "net.dv8tion", name = "JDA", version.ref = "jda" } jda5 = { group = "net.dv8tion", name = "JDA", version.ref = "jda5" } logback-core = { group = "ch.qos.logback", name = "logback-core", version.ref = "logback" } logback-classic = { group = "ch.qos.logback", name = "logback-classic", version.ref = "logback" } +kord = { group = "dev.kord", name = "kord-core", version.ref = "kord" } +kotlin-logging = { group = "io.github.oshai", name = "kotlin-logging-jvm", version.ref = "kotlinLogging" } +# Kotlin +coroutinesCore = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version.ref = "coroutines" } +coroutinesJdk8 = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-jdk8", version.ref = "coroutines" } # Test jupiter-engine = { group = "org.junit.jupiter", name = "junit-jupiter-engine", version.ref = "jupiterEngine" } jupiter-params = { group = "org.junit.jupiter", name = "junit-jupiter-params", version.ref = "jupiterEngine" } mockito-core = { group = "org.mockito", name = "mockito-core", version.ref = "mockitoCore" } mockito-jupiter = { group = "org.mockito", name = "mockito-junit-jupiter", version.ref = "mockitoJupiter" } +mockito-kotlin = { group = "org.mockito.kotlin", name = "mockito-kotlin", version.ref = "mockitoKotlin" } truth = { group = "com.google.truth", name = "truth", version.ref = "truth" } truth-java8 = { group = "com.google.truth.extensions", name = "truth-java8-extension", version.ref = "truth" } [bundles] +coroutines = ["coroutinesCore", "coroutinesJdk8"] diff --git a/settings.gradle.kts b/settings.gradle.kts index 0f120fa..0081de5 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -34,6 +34,9 @@ include(":cloud-discord-common") include(":cloud-javacord") include(":cloud-jda") include(":cloud-jda5") +include(":cloud-kord") include("examples/example-jda5") findProject(":examples/example-jda5")?.name = "example-jda5" +include("examples/example-kord") +findProject(":examples/example-kord")?.name = "example-kord"