diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml
index feae5a21..ba1cd0fd 100644
--- a/.github/workflows/maven.yml
+++ b/.github/workflows/maven.yml
@@ -21,8 +21,6 @@ jobs:
with:
java-version: '17'
distribution: 'adopt'
- cache: maven
- - name: Build with Maven
- run: mvn -B package --file pom.xml
+ cache: gradle
- name: Test with Maven
- run: mvn test
+ run: ./gradlew test
diff --git a/README.md b/README.md
index 34556d16..c1f0e0c8 100644
--- a/README.md
+++ b/README.md
@@ -1,231 +1,76 @@
-# 🍰 Nexus Framework
-The formal successor of [Velen](https://github.com/ShindouMihou/Velen) which takes a more OOP (Object Oriented Programming) approach to integrating slash commands onto your Discord bot without the need of builders. Nexus is slightly inspired by Laravel and is aimed to be more efficient than Velen at performing certain tasks.
+![Splashscreen](https://github.com/ShindouMihou/Nexus/assets/69381903/e2e2118b-07c4-4c49-9322-0507dc1ebf5c)
-## 🌉 Dependencies
-Nexus doesn't enforce other dependencies other than the latest stable version of Javacord. Every development version of Javacord will have a branch of Nexus that is dedicated to compatiability changes (if the development version includes a breaking change), we recommend including any SLF4J-compatiable logging framework although Nexus supports adapters for custom logging.
-- [💻 Logging](https://github.com/ShindouMihou/Nexus/#-Logging)
+#
-## 📦 Installation
-The framework doesn't have any plans of moving to Maven Central at this moment, as such, it is recommended to use [Jitpack.io](https://jitpack.io/#pw.mihou/Nexus) to install the framework onto your project. Please follow the instructions written there.
-- [pw.mihou.Nexus](https://jitpack.io/#pw.mihou/Nexus)
+
+ * In this scenario, the definition of a global command is a command that does not have an association
+ * with a server.
+ *
+ * @return All the global commands that were created inside the registry.
+ */
+ val globalCommands: Set
+ get() {
+ val commands: MutableSet = HashSet()
+
+ for (command in this.commands) {
+ if (command.isServerCommand) continue
+ commands.add(command)
+ }
+
+ return commands
+ }
+
+ /**
+ * Gets all the global context menus that are stored inside the Nexus registry.
+ */
+ val globalContextMenus: Set
+ get() {
+ val contextMenus: MutableSet = HashSet()
+ for (contextMenu in this.contextMenus) {
+ if (contextMenu.isServerOnly) continue
+ contextMenus.add(contextMenu)
+ }
+ return contextMenus
+ }
+
+ /**
+ * Gets all the server commands that are stored inside the Nexus registry of commands.
+ *
+ * In this scenario, the definition of a server command is a command that does have an association
+ * with a server.
+ *
+ * @return All the server commands that were created inside the registry.
+ */
+ val serverCommands: Set
+ get() {
+ val commands: MutableSet = HashSet()
+
+ for (command in this.commands) {
+ if (!command.isServerCommand) continue
+ commands.add(command)
+ }
+
+ return commands
+ }
+
+ /**
+ * Gets all the server-locked context menus that are stored inside the Nexus registry.
+ */
+ val serverContextMenus: Set
+ get() {
+ val contextMenus: MutableSet = HashSet()
+ for (contextMenu in this.contextMenus) {
+ if (!contextMenu.isServerOnly) continue
+ contextMenus.add(contextMenu)
+ }
+ return contextMenus
+ }
+
+ /**
+ * Gets all the commands that have an association with the given server.
+ *
+ * This method does a complete O(n) loop over the commands to identify any commands that matches the
+ * [List.contains] predicate over its server ids.
+ *
+ * @param server The server to find all associated commands of.
+ * @return All associated commands of the given server.
+ */
+ fun commandsAssociatedWith(server: Long): Set {
+ val commands: MutableSet = HashSet()
+
+ for (command in this.commands) {
+ if (!command.serverIds.contains(server)) continue
+ commands.add(command)
+ }
+
+ return commands
+ }
+
+ /**
+ * Gets all the context menus that have an association with the given server.
+ *
+ * This method does a complete O(n) loop over the context menus to identify any context menus that matches the
+ * [List.contains] predicate over its server ids.
+ *
+ * @param server The server to find all associated context menus of.
+ * @return All associated context menus of the given server.
+ */
+ fun contextMenusAssociatedWith(server: Long): Set {
+ val contextMenus: MutableSet = HashSet()
+ for (contextMenu in this.contextMenus) {
+ if (!contextMenu.serverIds.contains(server)) continue
+ contextMenus.add(contextMenu)
+ }
+ return contextMenus
+ }
+
+ fun add(command: NexusCommand): NexusCommandManager
+ fun add(contextMenu: NexusContextMenu): NexusCommandManager
+
+ operator fun get(applicationId: Long): NexusCommand?
+ operator fun get(uuid: String): NexusCommand?
+ operator fun get(name: String, server: Long? = null): NexusCommand?
+
+ fun getContextMenu(applicationId: Long): NexusContextMenu?
+ fun getContextMenu(uuid: String): NexusContextMenu?
+ fun getContextMenu(name: String, kind: ContextMenuKinds, server: Long? = null): NexusContextMenu?
+
+ /**
+ * Exports the indexes that was created which can then be used to create a database copy of the given indexes.
+ *
+ * It is not recommended to use this for any other purposes other than creating a database copy because this creates
+ * more garbage for the garbage collector.
+ *
+ * @return A snapshot of the indexes that the command manager has.
+ */
+ fun export(): List
+
+ /**
+ * This indexes all the commands whether it'd be global or server commands to increase
+ * performance and precision of slash commands.
+ */
+ fun index()
+
+ /**
+ * Creates an index mapping of the given command and the given slash command snowflake.
+ *
+ * You can use this method to index commands from your database.
+ *
+ * @param command The command that will be associated with the given snowflake.
+ * @param snowflake The snowflake that will be associated with the given command.
+ * @param server The server where this index should be associated with, can be null to mean global command.
+ */
+ fun index(command: Command, snowflake: Long, server: Long?)
+
+ /**
+ * Creates an index of all the slash commands provided. This will map all the commands based on properties
+ * that matches e.g. the name (since a command can only have one global and one server command that have the same name)
+ * and the server property if available.
+ *
+ * @param applicationCommandList the command list to use for indexing.
+ */
+ fun index(applicationCommandList: Set)
+
+ /**
+ * Creates an index of the given slash command provided. This will map all the command based on the property
+ * that matches e.g. the name (since a command can only have one global and one server command that have the same name)
+ * and the server property if available.
+ *
+ * @param applicationCommand The command to index.
+ */
+ fun index(applicationCommand: ApplicationCommand)
+
+ fun mentionMany(server: Long?, vararg commands: String) =
+ indexStore.mentionMany(server, *commands)
+ fun mentionOne(server: Long?, command: String, override: String? = null, default: String) =
+ indexStore.mentionOne(server, command, override, default)
+}
\ No newline at end of file
diff --git a/src/main/java/pw/mihou/nexus/core/managers/indexes/IndexStore.kt b/src/main/java/pw/mihou/nexus/core/managers/indexes/IndexStore.kt
new file mode 100644
index 00000000..84290aae
--- /dev/null
+++ b/src/main/java/pw/mihou/nexus/core/managers/indexes/IndexStore.kt
@@ -0,0 +1,94 @@
+package pw.mihou.nexus.core.managers.indexes
+
+import pw.mihou.nexus.core.managers.records.NexusMetaIndex
+
+interface IndexStore {
+
+ /**
+ * Adds the [NexusMetaIndex] into the store which can be retrieved later on by methods such as
+ * [get] when needed.
+ * @param metaIndex the index to add into the index store.
+ */
+ fun add(metaIndex: NexusMetaIndex)
+
+ /**
+ * Gets the [NexusMetaIndex] from the store or an in-memory cache by the application command identifier.
+ * @param applicationCommandId the application command identifier from Discord's side.
+ * @return the [NexusMetaIndex] that was caught otherwise none.
+ */
+ operator fun get(applicationCommandId: Long): NexusMetaIndex?
+
+ /**
+ * Gets the [NexusMetaIndex] that matches the given specifications.
+ * @param command the command name.
+ * @param server the server that this command belongs.
+ * @return the [NexusMetaIndex] that matches.
+ */
+ operator fun get(command: String, server: Long?): NexusMetaIndex?
+
+ /**
+ * Gets one command mention tag from the [IndexStore].
+ * @param server the server to fetch the commands from, if any.
+ * @param command the names of the commands to fetch.
+ * @param override overrides the name of the command, used to add subcommands and related.
+ * @param default the default value to use when there is no command like that.
+ * @return the mention tags of the command.
+ */
+ fun mentionOne(server: Long?, command: String, override: String? = null, default: String): String {
+ val index = get(command, server) ?: return default
+ return "${override ?: index.command}:${index.applicationCommandId}>"
+ }
+
+ /**
+ * Gets many command mention tags from the [IndexStore].
+ * @param server the server to fetch the commands from, if any.
+ * @param names the names of the commands to fetch.
+ * @return the mention tags of each commands.
+ */
+ fun mentionMany(server: Long?, vararg names: String): Map {
+ val indexes = many(server, *names)
+ val map = mutableMapOf()
+ for (index in indexes) {
+ map[index.command] = "${index.command}:${index.applicationCommandId}>"
+ }
+ return map
+ }
+
+ /**
+ * Gets one or more [NexusMetaIndex] from the store.
+ * @param applicationCommandIds the application command identifiers from Discord's side.
+ * @return the [NexusMetaIndex]es that matches.
+ */
+ fun many(vararg applicationCommandIds: Long): List
+
+ /**
+ * Gets one or more [NexusMetaIndex] from the store.
+ * @param server the server that these commands belongs to.
+ * @param names the names of the commands to fetch.
+ * @return the [NexusMetaIndex]es that matches.
+ */
+ fun many(server: Long?, vararg names: String): List
+
+ /**
+ * Adds one or more [NexusMetaIndex] into the store, this is used in scenarios such as mass-synchronization which
+ * offers more than one indexes at the same time.
+ *
+ * @param metaIndexes the indexes to add into the store.
+ */
+ fun addAll(metaIndexes: List)
+
+ /**
+ * Gets all the [NexusMetaIndex] available in the store, this is used more when the command manager's indexes are
+ * exported somewhere.
+ *
+ * @return all the [NexusMetaIndex] known in the store.
+ */
+ fun all(): List
+
+ /**
+ * Clears all the known indexes in the database. This happens when the command manager performs a re-indexing which
+ * happens when the developer themselves has called for it.
+ */
+ fun clear()
+
+}
\ No newline at end of file
diff --git a/src/main/java/pw/mihou/nexus/core/managers/indexes/defaults/InMemoryIndexStore.kt b/src/main/java/pw/mihou/nexus/core/managers/indexes/defaults/InMemoryIndexStore.kt
new file mode 100644
index 00000000..4e3786ec
--- /dev/null
+++ b/src/main/java/pw/mihou/nexus/core/managers/indexes/defaults/InMemoryIndexStore.kt
@@ -0,0 +1,38 @@
+package pw.mihou.nexus.core.managers.indexes.defaults
+
+import pw.mihou.nexus.core.managers.indexes.IndexStore
+import pw.mihou.nexus.core.managers.records.NexusMetaIndex
+
+class InMemoryIndexStore: IndexStore {
+
+ private val indexes: MutableMap = mutableMapOf()
+
+ override fun add(metaIndex: NexusMetaIndex) {
+ indexes[metaIndex.applicationCommandId] = metaIndex
+ }
+
+ override operator fun get(applicationCommandId: Long): NexusMetaIndex? = indexes[applicationCommandId]
+ override fun get(command: String, server: Long?): NexusMetaIndex? {
+ return indexes.values.firstOrNull { it.command == command && it.server == server }
+ }
+
+ override fun many(vararg applicationCommandIds: Long): List {
+ return applicationCommandIds.map(indexes::get).filterNotNull().toList()
+ }
+
+ override fun many(server: Long?, vararg names: String): List {
+ return indexes.values.filter { it.server == server && names.contains(it.command) }.toList()
+ }
+
+ override fun addAll(metaIndexes: List) {
+ for (metaIndex in metaIndexes) {
+ indexes[metaIndex.applicationCommandId] = metaIndex
+ }
+ }
+
+ override fun all(): List = indexes.values.toList()
+
+ override fun clear() {
+ indexes.clear()
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/pw/mihou/nexus/core/managers/indexes/exceptions/IndexIdentifierConflictException.kt b/src/main/java/pw/mihou/nexus/core/managers/indexes/exceptions/IndexIdentifierConflictException.kt
new file mode 100644
index 00000000..5bca0c33
--- /dev/null
+++ b/src/main/java/pw/mihou/nexus/core/managers/indexes/exceptions/IndexIdentifierConflictException.kt
@@ -0,0 +1,6 @@
+package pw.mihou.nexus.core.managers.indexes.exceptions
+
+class IndexIdentifierConflictException(name: String):
+ RuntimeException("An index-identifier conflict was identified between commands (or context menus) with the name $name. We do not " +
+ "recommend having commands (or context menus) with the same name that have the same unique identifier, please change one of the commands' (or context menus') identifier " +
+ "by using the @IdentifiableAs annotation. (https://github.com/ShindouMihou/Nexus/wiki/Slash-Command-Indexing)")
\ No newline at end of file
diff --git a/src/main/java/pw/mihou/nexus/core/managers/records/NexusMetaIndex.kt b/src/main/java/pw/mihou/nexus/core/managers/records/NexusMetaIndex.kt
new file mode 100644
index 00000000..b0ec96e4
--- /dev/null
+++ b/src/main/java/pw/mihou/nexus/core/managers/records/NexusMetaIndex.kt
@@ -0,0 +1,10 @@
+package pw.mihou.nexus.core.managers.records
+
+import pw.mihou.nexus.Nexus
+import pw.mihou.nexus.features.command.facade.NexusCommand
+import pw.mihou.nexus.features.contexts.NexusContextMenu
+
+data class NexusMetaIndex(val command: String, val applicationCommandId: Long, val server: Long?) {
+ fun takeCommand(): NexusCommand? = Nexus.commandManager[command]
+ fun takeContextMenu(): NexusContextMenu? = Nexus.commandManager.getContextMenu(command)
+}
diff --git a/src/main/java/pw/mihou/nexus/core/reflective/NexusReflection.kt b/src/main/java/pw/mihou/nexus/core/reflective/NexusReflection.kt
new file mode 100644
index 00000000..06707b70
--- /dev/null
+++ b/src/main/java/pw/mihou/nexus/core/reflective/NexusReflection.kt
@@ -0,0 +1,102 @@
+package pw.mihou.nexus.core.reflective
+
+import pw.mihou.nexus.Nexus
+import pw.mihou.nexus.core.assignment.NexusUuidAssigner
+import pw.mihou.nexus.core.reflective.annotations.*
+import pw.mihou.nexus.features.command.annotation.IdentifiableAs
+import java.lang.reflect.Field
+
+object NexusReflection {
+
+ /**
+ * Accumulates all the declared fields of the `from` parameter, this is useful for cases such as
+ * interceptor repositories.
+ *
+ * @param from the origin object where all the fields will be accumulated from.
+ * @param accumulator the accumulator to use.
+ */
+ fun accumulate(from: Any, accumulator: (field: Field) -> Unit) {
+ val instance = from::class.java
+ for (field in instance.declaredFields) {
+ field.isAccessible = true
+ accumulator(field)
+ }
+ }
+
+ /**
+ * Mirrors the fields from origin (`from`) to a new instance of the `to` class.
+ * This requires the `to` class to have an empty constructor (no parameters), otherwise this will not work.
+ *
+ * @param from the origin object where all the fields will be copied.
+ * @param to the new instance class where all the new fields will be pushed.
+ * @return a new instance with all the fields copied.
+ */
+ fun copy(from: Any, to: Class<*>): Any {
+ val instance = to.getDeclaredConstructor().newInstance()
+ val fields = NexusReflectionFields(from, instance)
+
+ if (to.isAnnotationPresent(MustImplement::class.java)) {
+ val extension = to.getAnnotation(MustImplement::class.java).clazz.java
+ if (!extension.isAssignableFrom(from::class.java)) {
+ throw IllegalStateException("${from::class.java.name} must implement the following class: ${extension.name}")
+ }
+ }
+
+ var uuid: String? = null
+ val uuidFields = fields.referencesWithAnnotation(Uuid::class.java)
+ if (uuidFields.isNotEmpty()) {
+ if (uuidFields.size == 1) {
+ uuid = fields.stringify(uuidFields[0].name)
+ } else {
+ for (field in uuidFields) {
+ if (uuid == null) {
+ uuid = fields.stringify(field.name)
+ } else {
+ uuid += ":" + fields.stringify(field.name)
+ }
+ }
+ }
+ }
+
+ if (from::class.java.isAnnotationPresent(IdentifiableAs::class.java)) {
+ uuid = from::class.java.getAnnotation(IdentifiableAs::class.java).key
+ }
+
+ for (field in instance::class.java.declaredFields) {
+ field.isAccessible = true
+
+ if (field.isAnnotationPresent(InjectReferenceClass::class.java)) {
+ field.set(instance, from)
+ } else if(field.isAnnotationPresent(InjectUUID::class.java)) {
+ if (uuid == null) {
+ uuid = NexusUuidAssigner.request()
+ }
+
+ field.set(instance, uuid)
+ } else if(field.isAnnotationPresent(Stronghold::class.java)) {
+ field.set(instance, fields.shared)
+ } else {
+ val value: Any = fields[field.name] ?: continue
+ val clazz = fromPrimitiveToNonPrimitive(field.type)
+ if (clazz.isAssignableFrom(value::class.java) || value::class.java == clazz) {
+ field.set(instance, value)
+ }
+ }
+ }
+
+ return instance
+ }
+
+ private fun fromPrimitiveToNonPrimitive(clazz: Class<*>): Class<*> {
+ return when (clazz) {
+ Boolean::class.java, Boolean::class.javaPrimitiveType -> Boolean::class.java
+ Int::class.java, Int::class.javaPrimitiveType -> Int::class.java
+ Long::class.java, Long::class.javaPrimitiveType -> Long::class.java
+ Char::class.java, Char::class.javaPrimitiveType -> Char::class.java
+ String::class.java -> String::class.java
+ Double::class.java, Double::class.javaPrimitiveType -> Double::class.java
+ else -> clazz
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/pw/mihou/nexus/core/reflective/NexusReflectionFields.kt b/src/main/java/pw/mihou/nexus/core/reflective/NexusReflectionFields.kt
new file mode 100644
index 00000000..d593ada6
--- /dev/null
+++ b/src/main/java/pw/mihou/nexus/core/reflective/NexusReflectionFields.kt
@@ -0,0 +1,184 @@
+package pw.mihou.nexus.core.reflective
+
+import pw.mihou.nexus.Nexus
+import pw.mihou.nexus.core.exceptions.NotInheritableException
+import pw.mihou.nexus.core.reflective.annotations.Required
+import pw.mihou.nexus.core.reflective.annotations.Share
+import pw.mihou.nexus.core.reflective.annotations.WithDefault
+import pw.mihou.nexus.features.command.core.NexusCommandCore
+import pw.mihou.nexus.features.inheritance.Inherits
+import java.lang.reflect.Constructor
+import java.lang.reflect.Field
+import java.lang.reflect.InvocationTargetException
+import java.util.*
+
+class NexusReflectionFields(private val from: Any, private val reference: Any) {
+
+ private val _fields = mutableMapOf()
+ private val _shared = mutableMapOf()
+
+ val shared: Map get() = Collections.unmodifiableMap(_shared)
+ val fields: Map get() = Collections.unmodifiableMap(_fields)
+
+ @Suppress("UNCHECKED_CAST")
+ operator fun get(key: String): R? {
+ return _fields[key.lowercase()]?.let { it as? R }
+ }
+
+ fun stringify(key: String): String? {
+ return _fields[key.lowercase()]?.let {
+ if (it is String) {
+ return it
+ }
+
+ return it.toString()
+ }
+ }
+
+ private val to = reference::class.java
+
+ init {
+ initDefaults()
+ if (reference is NexusCommandCore) {
+ Nexus.configuration.global.inheritance?.let { parent ->
+ if (parent::class.java.isAnnotationPresent(Inherits::class.java)) {
+ Nexus.logger.warn("Nexus doesn't support @Inherits on parent-level, instead, use superclasses " +
+ "such as abstract classes instead. Causing class: ${parent::class.java.name}.")
+ }
+ if (parent::class.java.superclass != null) {
+ load(parent::class.java.superclass, parent)
+ }
+ load(parent::class.java, parent)
+ }
+ }
+
+ if (from::class.java.isAnnotationPresent(Inherits::class.java)) {
+ val parent = from::class.java.getAnnotation(Inherits::class.java).value.java
+ if (parent.isAnnotationPresent(Inherits::class.java)) {
+ Nexus.logger.warn("Nexus doesn't support @Inherits on parent-level, instead, use superclasses " +
+ "such as abstract classes instead. Causing class: ${parent.name}.")
+ }
+ val instantiatedParent = instantiate(parent)
+ if (parent.superclass != null) {
+ load(parent.superclass, instantiatedParent)
+ }
+ load(instantiatedParent::class.java, instantiatedParent)
+ }
+
+ if (from::class.java.superclass != null) {
+ load(from::class.java.superclass)
+ }
+
+ load(from::class.java)
+ ensureHasRequired()
+ }
+
+ fun referenceWithAnnotation(annotation: Class): Field? {
+ return to.declaredFields.find { it.isAnnotationPresent(annotation) }
+ }
+
+ fun referencesWithAnnotation(annotation: Class): List {
+ return to.declaredFields.filter { it.isAnnotationPresent(annotation) }
+ }
+
+ /**
+ * Loads all the declared fields of the `reference` class that has the [WithDefault] annotation, this
+ * should be done first in order to have the fields be overridden when there is a value.
+ */
+ private fun initDefaults() {
+ for (field in to.declaredFields) {
+ if (!field.isAnnotationPresent(WithDefault::class.java)) continue
+ field.isAccessible = true
+ try {
+ _fields[field.name.lowercase()] = field.get(reference)
+ } catch (e: IllegalAccessException) {
+ throw IllegalStateException("Unable to complete reflection due to IllegalAccessException. [class=${to.name},field=${field.name}]")
+ }
+ }
+ }
+
+ /**
+ * Instantiates the `clazz`, if the class has a singleton instance, then it will use that instead. This requires
+ * the class to have a constructor that has no parameters, otherwise it will fail.
+ *
+ * @param clazz the class to instantiate.
+ * @return the instantiated class.
+ */
+ private fun instantiate(clazz: Class<*>): Any {
+ try {
+ val reference: Any
+ var singleton: Field? = null
+
+ // Detecting singleton instances, whether by Kotlin, or self-declared by the authors.
+ // This is important because we don't want to doubly-instantiate the instance.
+ try {
+ singleton = clazz.getField("INSTANCE")
+ } catch (_: NoSuchFieldException) {
+ for (field in clazz.declaredFields) {
+ if (field.name.equals("INSTANCE")
+ || field::class.java.name.equals(clazz.name)
+ || field::class.java == clazz) {
+ singleton = field
+ }
+ }
+ }
+
+ if (singleton != null) {
+ reference = singleton.get(null)
+ } else {
+ val constructor: Constructor<*> = if (clazz.constructors.isNotEmpty()) {
+ clazz.constructors.firstOrNull { it.parameterCount == 0 } ?: throw NotInheritableException(clazz)
+ } else {
+ clazz.getDeclaredConstructor()
+ }
+
+ constructor.isAccessible = true
+ reference = constructor.newInstance()
+ }
+
+ return reference
+ } catch (exception: Exception) {
+ when(exception) {
+ is InvocationTargetException, is InstantiationException, is IllegalAccessException, is NoSuchMethodException ->
+ throw IllegalStateException("Unable to instantiate class, likely no compatible constructor. [class=${clazz.name},error=${exception}]")
+ else -> throw exception
+ }
+ }
+ }
+
+ /**
+ * Pulls all the fields from the `clazz` to their respective fields depending on the annotation, for example, if there
+ * is a [Share] annotation present, it will be recorded under [sharedFields] otherwise it will be under [_fields].
+ *
+ * @param clazz the class to reference.
+ * @param ref the reference object.
+ */
+ private fun load(clazz : Class<*>, ref: Any = from) {
+ clazz.declaredFields.forEach {
+ it.isAccessible = true
+ try {
+ val value = it.get(ref) ?: return
+ if (it.isAnnotationPresent(Share::class.java)) {
+ _shared[it.name.lowercase()] = value
+ return@forEach
+ }
+
+ _fields[it.name.lowercase()] = value
+ } catch (e: IllegalAccessException) {
+ throw IllegalStateException("Unable to complete reflection due to IllegalAccessException. [class=${clazz.name},field=${it.name}]")
+ }
+ }
+ }
+
+ /**
+ * Ensures that all required fields (ones with [Required] annotation) has a value, otherwise throws an exception.
+ */
+ private fun ensureHasRequired() {
+ for (field in to.declaredFields) {
+ if (!field.isAnnotationPresent(Required::class.java)) continue
+ if (_fields[field.name.lowercase()] == null) {
+ throw IllegalStateException("${field.name} is a required field, therefore, needs to have a value in class ${from::class.java.name}.")
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/pw/mihou/nexus/core/reflective/NexusReflectiveCore.java b/src/main/java/pw/mihou/nexus/core/reflective/NexusReflectiveCore.java
deleted file mode 100755
index c178aac6..00000000
--- a/src/main/java/pw/mihou/nexus/core/reflective/NexusReflectiveCore.java
+++ /dev/null
@@ -1,86 +0,0 @@
-package pw.mihou.nexus.core.reflective;
-
-import pw.mihou.nexus.core.NexusCore;
-import pw.mihou.nexus.core.assignment.NexusUuidAssigner;
-import pw.mihou.nexus.core.reflective.annotations.*;
-import pw.mihou.nexus.core.reflective.core.NexusReflectiveVariableCore;
-import pw.mihou.nexus.core.reflective.facade.NexusReflectiveVariableFacade;
-import pw.mihou.nexus.features.command.core.NexusCommandCore;
-
-import java.util.*;
-
-public class NexusReflectiveCore {
-
- private static final Class> REFERENCE_CLASS = NexusCommandCore.class;
-
- public static NexusCommandCore command(Object object, NexusCore core) {
- NexusCommandCore reference = new NexusCommandCore();
-
- NexusReflectiveVariableFacade facade = new NexusReflectiveVariableCore(object, reference);
-
- if (REFERENCE_CLASS.isAnnotationPresent(MustImplement.class)) {
- Class> extension = REFERENCE_CLASS.getAnnotation(MustImplement.class).clazz();
-
- if (!extension.isAssignableFrom(object.getClass())) {
- throw new IllegalStateException("Nexus was unable to complete reflection stage because class: " +
- object.getClass().getName()
- + " must implement the following class: " + extension.getName());
- }
- }
-
-
- Arrays.stream(reference.getClass().getDeclaredFields())
- .forEach(field -> {
- field.setAccessible(true);
-
- try {
- if (field.isAnnotationPresent(InjectReferenceClass.class)) {
- field.set(reference, object);
- } else if (field.isAnnotationPresent(InjectUUID.class)) {
- field.set(reference, NexusUuidAssigner.request());
- } else if (field.isAnnotationPresent(InjectNexusCore.class)) {
- field.set(reference, core);
- } else if (field.isAnnotationPresent(Stronghold.class)){
- field.set(reference, facade.getSharedFields());
- } else {
- facade.getWithType(field.getName(), fromPrimitiveToNonPrimitive(field.getType())).ifPresent(o -> {
- try {
- field.set(reference, o);
- } catch (IllegalAccessException e) {
- e.printStackTrace();
- }
- });
- }
- } catch (IllegalAccessException e) {
- e.printStackTrace();
- }
- });
-
- return reference;
- }
-
- /**
- * This identifies primitive classes and returns their non-primitive
- * class values since for some reason, type-checking requires it.
- *
- * @param clazz The class to identify.
- * @return The non-primitive class variant.
- */
- private static Class> fromPrimitiveToNonPrimitive(Class> clazz) {
- if (clazz.equals(Boolean.class) || clazz.equals(boolean.class))
- return Boolean.class;
- else if (clazz.equals(Integer.class) || clazz.equals(int.class))
- return Integer.class;
- else if (clazz.equals(Long.class) || clazz.equals(long.class))
- return Long.class;
- else if (clazz.equals(Character.class) || clazz.equals(char.class))
- return Character.class;
- else if (clazz.equals(String.class))
- return String.class;
- else if (clazz.equals(Double.class) || clazz.equals(double.class))
- return Double.class;
- else
- return clazz;
- }
-
-}
diff --git a/src/main/java/pw/mihou/nexus/core/reflective/annotations/Uuid.kt b/src/main/java/pw/mihou/nexus/core/reflective/annotations/Uuid.kt
new file mode 100644
index 00000000..7e1daa1f
--- /dev/null
+++ b/src/main/java/pw/mihou/nexus/core/reflective/annotations/Uuid.kt
@@ -0,0 +1,5 @@
+package pw.mihou.nexus.core.reflective.annotations
+
+@Retention(AnnotationRetention.RUNTIME)
+@Target(AnnotationTarget.FIELD)
+annotation class Uuid()
diff --git a/src/main/java/pw/mihou/nexus/core/reflective/core/NexusReflectiveVariableCore.java b/src/main/java/pw/mihou/nexus/core/reflective/core/NexusReflectiveVariableCore.java
deleted file mode 100755
index 0de5ed9b..00000000
--- a/src/main/java/pw/mihou/nexus/core/reflective/core/NexusReflectiveVariableCore.java
+++ /dev/null
@@ -1,88 +0,0 @@
-package pw.mihou.nexus.core.reflective.core;
-
-import pw.mihou.nexus.core.reflective.annotations.Required;
-import pw.mihou.nexus.core.reflective.annotations.Share;
-import pw.mihou.nexus.core.reflective.annotations.WithDefault;
-import pw.mihou.nexus.core.reflective.facade.NexusReflectiveVariableFacade;
-import pw.mihou.nexus.features.command.core.NexusCommandCore;
-
-import javax.annotation.Nullable;
-import java.util.*;
-
-public class NexusReflectiveVariableCore implements NexusReflectiveVariableFacade {
-
- private final HashMap fields = new HashMap<>();
- private final HashMap sharedFields = new HashMap<>();
-
- public NexusReflectiveVariableCore(Object object, NexusCommandCore core) {
- // We'll collect all the fields with the WithDefault annotation from the reference class first.
- // then utilize those fields when we need a default value. Please ensure that the field always
- // has a value beforehand.
-
- Arrays.stream(NexusCommandCore.class.getDeclaredFields())
- .filter(field -> field.isAnnotationPresent(WithDefault.class))
- .peek(field -> field.setAccessible(true))
- .forEach(field -> {
- try {
- fields.put(field.getName().toLowerCase(), field.get(core));
- } catch (IllegalAccessException e) {
- e.printStackTrace();
- throw new IllegalStateException(
- "Nexus was unable to complete variable reflection stage for class: " + NexusCommandCore.class.getName()
- );
- }
- });
-
- // After collecting all the defaults, we can start bootstrapping the fields HashMap.
- Arrays.stream(object.getClass().getDeclaredFields()).forEach(field -> {
- field.setAccessible(true);
-
- try {
- Object obj = field.get(object);
-
- if (obj == null) {
- return;
- }
-
- if (field.isAnnotationPresent(Share.class)) {
- sharedFields.put(field.getName().toLowerCase(), obj);
- return;
- }
-
- fields.put(field.getName().toLowerCase(), obj);
- } catch (IllegalAccessException e) {
- e.printStackTrace();
- throw new IllegalStateException(
- "Nexus was unable to complete variable reflection stage for class: " + object.getClass().getName()
- );
- }
- });
-
- // Handling required fields, the difference between `clazz` and `object.getClass()`
- // is that `clazz` refers to the NexusCommandImplementation while `object` refers
- // to the developer-defined object.
- Arrays.stream(NexusCommandCore.class.getDeclaredFields())
- .filter(field -> field.isAnnotationPresent(Required.class))
- .forEach(field -> {
- @Nullable Object obj = fields.get(field.getName().toLowerCase());
- if (obj == null) {
- throw new IllegalStateException(
- "Nexus was unable to complete variable reflection stage for class: " + object.getClass().getName() +
- " because the field: " + field.getName() + " is required to have a value."
- );
- }
- });
- }
-
- @Override
- @SuppressWarnings("unchecked")
- public Optional get(String field) {
- return fields.containsKey(field.toLowerCase()) ? Optional.of((R) fields.get(field.toLowerCase())) : Optional.empty();
-
- }
-
- @Override
- public Map getSharedFields() {
- return Collections.unmodifiableMap(sharedFields);
- }
-}
diff --git a/src/main/java/pw/mihou/nexus/core/reflective/facade/NexusReflectiveVariableFacade.java b/src/main/java/pw/mihou/nexus/core/reflective/facade/NexusReflectiveVariableFacade.java
deleted file mode 100755
index 5ecd7597..00000000
--- a/src/main/java/pw/mihou/nexus/core/reflective/facade/NexusReflectiveVariableFacade.java
+++ /dev/null
@@ -1,47 +0,0 @@
-package pw.mihou.nexus.core.reflective.facade;
-
-import java.util.Map;
-import java.util.Optional;
-
-/**
- * This facade is dedicated to {@link pw.mihou.nexus.core.reflective.core.NexusReflectiveVariableCore} which
- * is utilized by {@link pw.mihou.nexus.core.reflective.NexusReflectiveCore} to grab variables with reflection
- * and so forth while abiding by the rules of default values.
- */
-public interface NexusReflectiveVariableFacade {
-
- /**
- * Gets the value of the field with the specified name.
- *
- * @param field The field name to fetch.
- * @param The type to expect returned.
- * @return The field value if present.
- */
- Optional get(String field);
-
- /**
- * Gets the map containing all the shared fields.
- *
- * @return An unmodifiable map that contains all the shared
- * fields that were defined in the class.
- */
- Map getSharedFields();
-
- /**
- * Gets the value of the field with the specified name that
- * matches the specific type.
- *
- * @param field The field name to fetch.
- * @param rClass The type in class to expect.
- * @param The type to expect returned.
- * @return The field value if present and also matches the type.
- */
- @SuppressWarnings("unchecked")
- default Optional getWithType(String field, Class rClass) {
- return get(field).filter(o ->
- rClass.isAssignableFrom(o.getClass())
- || o.getClass().equals(rClass)
- ).map(o -> (R) o);
- }
-
-}
diff --git a/src/main/java/pw/mihou/nexus/express/NexusExpress.kt b/src/main/java/pw/mihou/nexus/express/NexusExpress.kt
new file mode 100644
index 00000000..7015cbd0
--- /dev/null
+++ b/src/main/java/pw/mihou/nexus/express/NexusExpress.kt
@@ -0,0 +1,87 @@
+package pw.mihou.nexus.express
+
+import org.javacord.api.DiscordApi
+import org.javacord.api.entity.server.Server
+import pw.mihou.nexus.express.event.NexusExpressEvent
+import pw.mihou.nexus.express.request.NexusExpressRequest
+import java.util.concurrent.CompletableFuture
+import java.util.function.Consumer
+import java.util.function.Predicate
+
+interface NexusExpress {
+
+ /**
+ * Queues an event to be executed by the specific shard that
+ * it is specified for.
+ *
+ * @param shard The shard to handle this event.
+ * @param event The event to execute for this shard.
+ * @return The controller and status viewer for the event.
+ */
+ fun queue(shard: Int, event: NexusExpressRequest): NexusExpressEvent
+
+ /**
+ * Queues an event to be executed by the shard that matches the given predicate.
+ *
+ * @param predicate The predicate that the shard should match.
+ * @param event The event to execute for this shard.
+ * @return The controller and status viewer for the event.
+ */
+ fun queue(predicate: Predicate, event: NexusExpressRequest): NexusExpressEvent
+
+ /**
+ * Queues an event to be executed by any specific shard that is available
+ * to take the event.
+ *
+ * @param event The event to execute by a shard.
+ * @return The controller and status viewer for the event.
+ */
+ fun queue(event: NexusExpressRequest): NexusExpressEvent
+
+ /**
+ * Creates an awaiting listener to wait for a given shard to be ready and returns
+ * the shard itself if it is ready.
+ *
+ * @param shard The shard number to wait to complete.
+ * @return The [DiscordApi] instance of the given shard.
+ */
+ fun await(shard: Int): CompletableFuture
+
+ /**
+ * Creates an awaiting listener to wait for a given shard that has the given server.
+ *
+ * @param server The server to wait for a shard to contain.
+ * @return The [DiscordApi] instance of the given shard.
+ */
+ fun await(server: Long): CompletableFuture
+
+ /**
+ * Creates an awaiting listener to wait for any available shards to be ready and returns
+ * the shard that is available.
+ *
+ * @return The [DiscordApi] available to take any action.
+ */
+ fun awaitAvailable(): CompletableFuture
+
+ /**
+ * A short-hand method to cause a failed future whenever the event has expired or has failed to be
+ * processed which can happen at times.
+ *
+ * It is recommended to use ExpressWay with CompletableFutures as this provides an extra kill-switch
+ * whenever a shard somehow isn't available after the expiration time.
+ *
+ * @param event The event that was queued.
+ * @param future The future to fail when the status of the event has failed.
+ * @param A random type.
+ */
+ fun failFutureOnExpire(event: NexusExpressEvent, future: CompletableFuture)
+
+ /**
+ * Broadcasts the event for all shards to execute, this doesn't wait for any shards
+ * that aren't available during the time that it was executed.
+ *
+ * @param event The event to broadcast to all shards.
+ */
+ fun broadcast(event: Consumer)
+
+}
\ No newline at end of file
diff --git a/src/main/java/pw/mihou/nexus/express/core/NexusExpressCore.kt b/src/main/java/pw/mihou/nexus/express/core/NexusExpressCore.kt
new file mode 100644
index 00000000..2d9b53a1
--- /dev/null
+++ b/src/main/java/pw/mihou/nexus/express/core/NexusExpressCore.kt
@@ -0,0 +1,251 @@
+package pw.mihou.nexus.express.core
+
+import org.javacord.api.DiscordApi
+import org.javacord.api.entity.server.Server
+import pw.mihou.nexus.Nexus
+import pw.mihou.nexus.core.exceptions.NexusFailedActionException
+import pw.mihou.nexus.express.NexusExpress
+import pw.mihou.nexus.express.event.NexusExpressEvent
+import pw.mihou.nexus.express.event.core.NexusExpressEventCore
+import pw.mihou.nexus.express.event.status.NexusExpressEventStatus
+import pw.mihou.nexus.express.request.NexusExpressRequest
+import java.util.concurrent.*
+import java.util.concurrent.locks.ReentrantLock
+import java.util.function.Consumer
+import java.util.function.Predicate
+import kotlin.concurrent.withLock
+
+internal class NexusExpressCore: NexusExpress {
+
+ private val globalQueue: BlockingQueue = LinkedBlockingQueue()
+ private val predicateQueue: BlockingQueue, NexusExpressEvent>> = LinkedBlockingQueue()
+
+ private val predicateQueueProcessingLock = ReentrantLock()
+ private val globalQueueProcessingLock = ReentrantLock()
+
+ private val localQueue: MutableMap> = ConcurrentHashMap()
+
+ fun ready(shard: DiscordApi) {
+ Nexus.launcher.launch {
+ val local = localQueue(shard.currentShard)
+ while (!local.isEmpty()) {
+ try {
+ val event = local.poll()
+
+ if (event != null) {
+ Nexus.launcher.launch {
+ (event as NexusExpressEventCore).process(shard)
+ }
+ }
+ } catch (exception: Exception) {
+ Nexus.logger.error("An uncaught exception was caught from Nexus Express Way.", exception)
+ }
+ }
+
+ predicateQueueProcessingLock.withLock {
+ while (!predicateQueue.isEmpty()) {
+ try {
+ val (predicate, _) = predicateQueue.peek()
+
+ if (!predicate.test(shard)) continue
+ val (_, event) = predicateQueue.poll()
+
+ Nexus.launcher.launch {
+ (event as NexusExpressEventCore).process(shard)
+ }
+ } catch (exception: Exception) {
+ Nexus.logger.error("An uncaught exception was caught from Nexus Express Way.", exception)
+ }
+ }
+ }
+ }
+
+ Nexus.launcher.launch {
+ globalQueueProcessingLock.withLock {
+ while(globalQueue.isNotEmpty()) {
+ try {
+ val event = globalQueue.poll()
+
+ if (event != null) {
+ Nexus.launcher.launch {
+ (event as NexusExpressEventCore).process(shard)
+ }
+ }
+ } catch (exception: Exception) {
+ Nexus.logger.error("An uncaught exception was caught from Nexus Express Way.", exception)
+ }
+ }
+ }
+ }
+ }
+
+ private fun localQueue(shard: Int): BlockingQueue {
+ return localQueue.computeIfAbsent(shard) { LinkedBlockingQueue() }
+ }
+
+ override fun queue(shard: Int, event: NexusExpressRequest): NexusExpressEvent {
+ val expressEvent = NexusExpressEventCore(event)
+
+ if (Nexus.sharding[shard] == null){
+ localQueue(shard).add(expressEvent)
+
+ val maximumTimeout = Nexus.configuration.express.maximumTimeout
+ if (!maximumTimeout.isZero && !maximumTimeout.isNegative) {
+ Nexus.launch.scheduler.launch(maximumTimeout.toMillis()) {
+ expressEvent.`do` {
+ if (status() == NexusExpressEventStatus.WAITING) {
+ val removed = localQueue(shard).remove(this)
+ if (Nexus.configuration.express.showExpiredWarnings) {
+ Nexus.logger.warn(
+ "An express request that was specified " +
+ "for shard $shard has expired after ${maximumTimeout.toMillis()} milliseconds " +
+ "without the shard connecting with Nexus. [acknowledged=$removed]"
+ )
+ }
+
+ expire()
+ }
+ }
+ }
+ }
+ } else {
+ Nexus.launcher.launch { expressEvent.process(Nexus.sharding[shard]!!) }
+ }
+
+ return expressEvent
+ }
+
+ override fun queue(predicate: Predicate, event: NexusExpressRequest): NexusExpressEvent {
+ val expressEvent = NexusExpressEventCore(event)
+ val shard = Nexus.sharding.find { shard2 -> predicate.test(shard2) }
+
+ if (shard == null){
+ val pair = predicate to expressEvent
+ predicateQueue.add(pair)
+
+ val maximumTimeout = Nexus.configuration.express.maximumTimeout
+ if (!maximumTimeout.isZero && !maximumTimeout.isNegative) {
+ Nexus.launch.scheduler.launch(maximumTimeout.toMillis()) {
+ expressEvent.`do` {
+ if (status() == NexusExpressEventStatus.WAITING) {
+ val removed = predicateQueue.remove(pair)
+ if (Nexus.configuration.express.showExpiredWarnings) {
+ Nexus.logger.warn(
+ "An express request that was specified " +
+ "for a predicate has expired after ${maximumTimeout.toMillis()} milliseconds " +
+ "without any matching shard connecting with Nexus. [acknowledged=$removed]"
+ )
+ }
+
+ expire()
+ }
+ }
+ }
+ }
+ } else {
+ Nexus.launcher.launch { expressEvent.process(shard) }
+ }
+
+ return expressEvent
+ }
+
+ override fun queue(event: NexusExpressRequest): NexusExpressEvent {
+ val expressEvent = NexusExpressEventCore(event)
+
+ if (Nexus.sharding.size == 0){
+ globalQueue.add(expressEvent)
+
+ val maximumTimeout = Nexus.configuration.express.maximumTimeout
+ if (!maximumTimeout.isZero && !maximumTimeout.isNegative) {
+ Nexus.launch.scheduler.launch(maximumTimeout.toMillis()) {
+ expressEvent.`do` {
+ if (status() == NexusExpressEventStatus.WAITING) {
+ val removed = globalQueue.remove(this)
+ if (Nexus.configuration.express.showExpiredWarnings) {
+ Nexus.logger.warn(
+ "An express request that was specified " +
+ "for any available shards has expired after ${maximumTimeout.toMillis()} milliseconds " +
+ "without any shard connecting with Nexus. [acknowledged=$removed]"
+ )
+ }
+
+ expire()
+ }
+ }
+ }
+ }
+ } else {
+ Nexus.launcher.launch { expressEvent.process(Nexus.sharding.collection().first()) }
+ }
+
+ return expressEvent
+ }
+
+ override fun await(shard: Int): CompletableFuture {
+ val shardA = Nexus.sharding[shard]
+
+ if (shardA != null) {
+ return CompletableFuture.completedFuture(shardA)
+ }
+
+ val future = CompletableFuture()
+ failFutureOnExpire(queue(shard, future::complete), future)
+
+ return future
+ }
+
+ override fun await(server: Long): CompletableFuture {
+ val serverA = Nexus.sharding.server(server)
+
+ if (serverA != null) {
+ return CompletableFuture.completedFuture(serverA)
+ }
+
+ val future = CompletableFuture()
+ failFutureOnExpire(
+ queue(
+ { shard -> shard.getServerById(server).isPresent },
+ { shard -> future.complete(shard.getServerById(server).get()) }
+ ),
+ future
+ )
+
+ return future
+ }
+
+ override fun awaitAvailable(): CompletableFuture {
+ val shardA = Nexus.sharding.collection().firstOrNull()
+
+ if (shardA != null) {
+ return CompletableFuture.completedFuture(shardA)
+ }
+
+ val future = CompletableFuture()
+ failFutureOnExpire(queue(future::complete), future)
+
+ return future
+ }
+
+ override fun failFutureOnExpire(event: NexusExpressEvent, future: CompletableFuture) {
+ event.addStatusChangeListener { _, _, newStatus ->
+ if (newStatus == NexusExpressEventStatus.EXPIRED || newStatus == NexusExpressEventStatus.STOPPED) {
+ future.completeExceptionally(
+ NexusFailedActionException("Failed to connect with the shard that was being waited, it's possible " +
+ "that the maximum timeout has been reached or the event has been somehow cancelled.")
+ )
+ }
+ }
+ }
+
+ override fun broadcast(event: Consumer) {
+ Nexus.sharding.collection().forEach { shard ->
+ Nexus.launcher.launch {
+ try {
+ event.accept(shard)
+ } catch (exception: Exception) {
+ Nexus.logger.error("An uncaught exception was caught from Nexus Express Broadcaster.", exception)
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/pw/mihou/nexus/express/event/NexusExpressEvent.kt b/src/main/java/pw/mihou/nexus/express/event/NexusExpressEvent.kt
new file mode 100644
index 00000000..7316a7e4
--- /dev/null
+++ b/src/main/java/pw/mihou/nexus/express/event/NexusExpressEvent.kt
@@ -0,0 +1,84 @@
+package pw.mihou.nexus.express.event
+
+import pw.mihou.nexus.Nexus
+import pw.mihou.nexus.express.event.listeners.NexusExpressEventStatusChange
+import pw.mihou.nexus.express.event.listeners.NexusExpressEventStatusListener
+import pw.mihou.nexus.express.event.status.NexusExpressEventStatus
+
+interface NexusExpressEvent {
+
+ /**
+ * Signals the event to be cancelled. This method changes the status of the
+ * event from [NexusExpressEventStatus.WAITING] to [NexusExpressEventStatus.STOPPED].
+ *
+ * The signal will be ignored if the cancel was executed when the status is not equivalent to [NexusExpressEventStatus.WAITING].
+ */
+ fun cancel()
+
+ /**
+ * Gets the current status of the event.
+ * @return the current status of the event.
+ */
+ fun status(): NexusExpressEventStatus
+
+ /**
+ * Adds a [NexusExpressEventStatusChange] listener to the event, allowing you to receive events
+ * about when the status of the event changes.
+ * @param event the listener to use to listen to the event.
+ */
+ fun addStatusChangeListener(event: NexusExpressEventStatusChange)
+
+ /**
+ * Adds a specific [NexusExpressEventStatusListener] that can listen to specific, multiple status changes
+ * and react to it. This is a short-hand method of [addStatusChangeListener] and is used to handle specific
+ * status change events.
+ * @param ev the listener to use to listen to the event.
+ * @param statuses the new statuses that will trigger the listener.
+ */
+ fun addStatusChangeListener(ev: NexusExpressEventStatusListener, vararg statuses: NexusExpressEventStatus) {
+ this.addStatusChangeListener status@{ event, _, newStatus ->
+ for (status in statuses) {
+ if (newStatus == status) {
+ try {
+ ev.onStatusChange(event)
+ } catch (ex: Exception) {
+ Nexus.logger.error("Caught an uncaught exception in a Express Way listener.", ex)
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Adds a [NexusExpressEventStatusListener] that listens specifically to events that causes the listener to
+ * cancel, such in the case of a [cancel] or an expire. If you want to listen specifically to a call to [cancel],
+ * we recommend using [addStoppedListener] listener instead.
+ * @param ev the listener to use to listen to the event.
+ */
+ fun addCancelListener(ev: NexusExpressEventStatusListener) =
+ this.addStatusChangeListener(ev, NexusExpressEventStatus.STOPPED, NexusExpressEventStatus.EXPIRED)
+
+ /**
+ * Adds a [NexusExpressEventStatusListener] that listens specifically to when the event is finished processing.
+ * @param ev the listener to use to listen to the event.
+ */
+ fun addFinishedListener(ev: NexusExpressEventStatusListener) =
+ this.addStatusChangeListener(ev, NexusExpressEventStatus.FINISHED)
+
+ /**
+ * Adds a [NexusExpressEventStatusListener] that listens specifically to when the event expired while waiting.
+ * @param ev the listener to use to listen to the event.
+ */
+ fun addExpiredListener(ev: NexusExpressEventStatusListener) =
+ this.addStatusChangeListener(ev, NexusExpressEventStatus.EXPIRED)
+
+ /**
+ * Adds a [NexusExpressEventStatusListener] that listens specifically to events that causes the listener to
+ * [cancel]. If you want to listen specifically to a call to [cancel] and expire, we recommend using
+ * [addCancelListener] listener instead which listens to both cancel and expire.
+ * @param ev the listener to use to listen to the event.
+ */
+ fun addStoppedListener(ev: NexusExpressEventStatusListener) =
+ this.addStatusChangeListener(ev, NexusExpressEventStatus.STOPPED)
+
+}
\ No newline at end of file
diff --git a/src/main/java/pw/mihou/nexus/express/event/core/NexusExpressEventCore.kt b/src/main/java/pw/mihou/nexus/express/event/core/NexusExpressEventCore.kt
new file mode 100644
index 00000000..7b430cda
--- /dev/null
+++ b/src/main/java/pw/mihou/nexus/express/event/core/NexusExpressEventCore.kt
@@ -0,0 +1,74 @@
+package pw.mihou.nexus.express.event.core
+
+import org.javacord.api.DiscordApi
+import pw.mihou.nexus.Nexus
+import pw.mihou.nexus.express.event.NexusExpressEvent
+import pw.mihou.nexus.express.event.listeners.NexusExpressEventStatusChange
+import pw.mihou.nexus.express.event.status.NexusExpressEventStatus
+import pw.mihou.nexus.express.request.NexusExpressRequest
+
+internal class NexusExpressEventCore(val request: NexusExpressRequest): NexusExpressEvent {
+
+ @Volatile private var status = NexusExpressEventStatus.WAITING
+ private val listeners = mutableListOf()
+
+ override fun cancel() {
+ synchronized(this) {
+ if (status == NexusExpressEventStatus.WAITING) {
+ change(status = NexusExpressEventStatus.STOPPED)
+ }
+ }
+ }
+
+ fun expire() {
+ synchronized (this) {
+ if (status == NexusExpressEventStatus.WAITING) {
+ change(status = NexusExpressEventStatus.EXPIRED)
+ }
+ }
+ }
+
+ fun `do`(task: NexusExpressEventCore.() -> Unit) {
+ synchronized(this) {
+ task()
+ }
+ }
+
+ fun process(shard: DiscordApi) {
+ synchronized(this) {
+ try {
+ if (status == NexusExpressEventStatus.STOPPED || status == NexusExpressEventStatus.EXPIRED) {
+ return@synchronized
+ }
+
+ change(status = NexusExpressEventStatus.PROCESSING)
+ request.onEvent(shard)
+ } catch (exception: Exception) {
+ Nexus.configuration.global.logger.error("An uncaught exception was caught by Nexus Express Way.", exception)
+ } finally {
+ change(status = NexusExpressEventStatus.FINISHED)
+ }
+ }
+ }
+
+ private fun change(status: NexusExpressEventStatus) {
+ synchronized(this) {
+ val oldStatus = this.status
+ this.status = status
+
+ listeners.forEach { listener -> listener.onStatusChange(this, oldStatus, status) }
+ }
+ }
+
+ override fun status(): NexusExpressEventStatus {
+ synchronized(this) {
+ return status
+ }
+ }
+
+ override fun addStatusChangeListener(event: NexusExpressEventStatusChange) {
+ synchronized(this) {
+ listeners.add(event)
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/pw/mihou/nexus/express/event/listeners/NexusExpressEventStatusChange.kt b/src/main/java/pw/mihou/nexus/express/event/listeners/NexusExpressEventStatusChange.kt
new file mode 100644
index 00000000..1c1b8169
--- /dev/null
+++ b/src/main/java/pw/mihou/nexus/express/event/listeners/NexusExpressEventStatusChange.kt
@@ -0,0 +1,10 @@
+package pw.mihou.nexus.express.event.listeners
+
+import pw.mihou.nexus.express.event.NexusExpressEvent
+import pw.mihou.nexus.express.event.status.NexusExpressEventStatus
+
+fun interface NexusExpressEventStatusChange {
+
+ fun onStatusChange(event: NexusExpressEvent, oldStatus: NexusExpressEventStatus, newStatus: NexusExpressEventStatus)
+
+}
\ No newline at end of file
diff --git a/src/main/java/pw/mihou/nexus/express/event/listeners/NexusExpressEventStatusListener.kt b/src/main/java/pw/mihou/nexus/express/event/listeners/NexusExpressEventStatusListener.kt
new file mode 100644
index 00000000..cd3ea565
--- /dev/null
+++ b/src/main/java/pw/mihou/nexus/express/event/listeners/NexusExpressEventStatusListener.kt
@@ -0,0 +1,7 @@
+package pw.mihou.nexus.express.event.listeners
+
+import pw.mihou.nexus.express.event.NexusExpressEvent
+
+fun interface NexusExpressEventStatusListener {
+ fun onStatusChange(event: NexusExpressEvent)
+}
\ No newline at end of file
diff --git a/src/main/java/pw/mihou/nexus/express/event/status/NexusExpressEventStatus.kt b/src/main/java/pw/mihou/nexus/express/event/status/NexusExpressEventStatus.kt
new file mode 100644
index 00000000..4bd2e389
--- /dev/null
+++ b/src/main/java/pw/mihou/nexus/express/event/status/NexusExpressEventStatus.kt
@@ -0,0 +1,5 @@
+package pw.mihou.nexus.express.event.status
+
+enum class NexusExpressEventStatus {
+ EXPIRED, STOPPED, WAITING, PROCESSING, FINISHED
+}
\ No newline at end of file
diff --git a/src/main/java/pw/mihou/nexus/express/request/NexusExpressRequest.kt b/src/main/java/pw/mihou/nexus/express/request/NexusExpressRequest.kt
new file mode 100644
index 00000000..35c08010
--- /dev/null
+++ b/src/main/java/pw/mihou/nexus/express/request/NexusExpressRequest.kt
@@ -0,0 +1,9 @@
+package pw.mihou.nexus.express.request
+
+import org.javacord.api.DiscordApi
+
+fun interface NexusExpressRequest {
+
+ fun onEvent(shard: DiscordApi)
+
+}
\ No newline at end of file
diff --git a/src/main/java/pw/mihou/nexus/features/command/annotation/IdentifiableAs.kt b/src/main/java/pw/mihou/nexus/features/command/annotation/IdentifiableAs.kt
new file mode 100644
index 00000000..a922cd69
--- /dev/null
+++ b/src/main/java/pw/mihou/nexus/features/command/annotation/IdentifiableAs.kt
@@ -0,0 +1,13 @@
+package pw.mihou.nexus.features.command.annotation
+
+import pw.mihou.nexus.Nexus
+
+/**
+ * An annotation that tells [Nexus] that the command should use the given key as its unique identifier.
+ *
+ * This tends to be used when a command's name is common among other commands, which therefore causes an index-identifier conflict,
+ * and needs to be resolved by using this annotation to change the identifier of the command.
+ */
+@Retention(AnnotationRetention.RUNTIME)
+@Target(AnnotationTarget.CLASS)
+annotation class IdentifiableAs(val key: String)
diff --git a/src/main/java/pw/mihou/nexus/features/command/annotation/NexusAttach.java b/src/main/java/pw/mihou/nexus/features/command/annotation/NexusAttach.java
deleted file mode 100755
index 1ff6ce8b..00000000
--- a/src/main/java/pw/mihou/nexus/features/command/annotation/NexusAttach.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package pw.mihou.nexus.features.command.annotation;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * This tells Nexus to attach the command onto the Nexus command directory
- * once it is finished processing the command.
- */
-@Retention(RetentionPolicy.RUNTIME)
-@Target(ElementType.TYPE)
-@Deprecated(forRemoval = true)
-public @interface NexusAttach {
-}
diff --git a/src/main/java/pw/mihou/nexus/features/command/core/NexusCommandCore.java b/src/main/java/pw/mihou/nexus/features/command/core/NexusCommandCore.java
index d7568e10..bfbe9839 100755
--- a/src/main/java/pw/mihou/nexus/features/command/core/NexusCommandCore.java
+++ b/src/main/java/pw/mihou/nexus/features/command/core/NexusCommandCore.java
@@ -3,12 +3,12 @@
import org.javacord.api.entity.permission.PermissionType;
import org.javacord.api.interaction.DiscordLocale;
import org.javacord.api.interaction.SlashCommandOption;
-import pw.mihou.nexus.core.NexusCore;
+import org.jetbrains.annotations.NotNull;
import pw.mihou.nexus.core.reflective.annotations.*;
+import pw.mihou.nexus.features.command.validation.OptionValidation;
import pw.mihou.nexus.features.command.facade.NexusCommand;
import pw.mihou.nexus.features.command.facade.NexusHandler;
-import java.time.Duration;
import java.util.*;
import java.util.stream.Stream;
@@ -28,6 +28,7 @@ public class NexusCommandCore implements NexusCommand {
private Map nexusCustomFields;
@Required
+ @Uuid
public String name;
@WithDefault
@@ -43,35 +44,34 @@ public class NexusCommandCore implements NexusCommand {
public List options = Collections.emptyList();
@WithDefault
- public Duration cooldown = Duration.ofSeconds(5);
+ public List> validators = Collections.emptyList();
@WithDefault
public List middlewares = Collections.emptyList();
@WithDefault
public List afterwares = Collections.emptyList();
-
@WithDefault
public List serverIds = new ArrayList<>();
-
@WithDefault
public boolean defaultEnabledForEveryone = true;
-
@WithDefault
public boolean enabledInDms = true;
-
@WithDefault
public boolean defaultDisabled = false;
-
+ @WithDefault
+ public boolean nsfw = false;
@WithDefault
public List defaultEnabledForPermissions = Collections.emptyList();
-
- @InjectNexusCore
- public NexusCore core;
-
@InjectReferenceClass
public NexusHandler handler;
+ @NotNull
+ @Override
+ public String getUuid() {
+ return uuid;
+ }
+
@Override
public String getName() {
return name;
@@ -87,28 +87,22 @@ public List getOptions() {
return options;
}
- @Override
- public Duration getCooldown() {
- return cooldown;
- }
-
@Override
public List getServerIds() {
return serverIds;
}
@Override
- public NexusCommand addSupportFor(Long... serverIds) {
+ public NexusCommand associate(Long... serverIds) {
this.serverIds = Stream.concat(this.serverIds.stream(), Stream.of(serverIds)).toList();
return this;
}
@Override
- public NexusCommand removeSupportFor(Long... serverIds) {
- List mutableList = new ArrayList<>(this.serverIds);
- mutableList.removeAll(Arrays.stream(serverIds).toList());
+ public NexusCommand disassociate(Long... serverIds) {
+ List excludedSnowflakes = Arrays.asList(serverIds);
+ this.serverIds = this.serverIds.stream().filter(snowflake -> !excludedSnowflakes.contains(snowflake)).toList();
- this.serverIds = mutableList.stream().toList();
return this;
}
@@ -143,6 +137,11 @@ public boolean isDefaultDisabled() {
return defaultDisabled;
}
+ @Override
+ public boolean isNsfw() {
+ return nsfw;
+ }
+
@Override
public List getDefaultEnabledForPermissions() {
return defaultEnabledForPermissions;
@@ -158,19 +157,23 @@ public Map getDescriptionLocalizations() {
return descriptionLocalizations;
}
- @Override
- public long getServerId() {
- return serverIds.get(0);
- }
-
@Override
public String toString() {
return "NexusCommandCore{" +
"name='" + name + '\'' +
", description='" + description + '\'' +
", options=" + options +
- ", cooldown=" + cooldown +
", serverId=" + getServerIds().toString() +
+ ", middlewares=" + middlewares.toString() +
+ ", afterwares=" + afterwares.toString() +
+ ", nameLocalizations=" + nameLocalizations.toString() +
+ ", descriptionLocalizations=" + descriptionLocalizations.toString() +
+ ", defaultEnabledForPermissions=" + defaultEnabledForPermissions.toString() +
+ ", shared=" + nexusCustomFields.toString() +
+ ", defaultDisabled=" + defaultDisabled +
+ ", defaultEnabledForEveryone=" + defaultEnabledForEveryone +
+ ", enabledInDms=" + enabledInDms +
+ ", nsfw=" + nsfw +
'}';
}
}
diff --git a/src/main/java/pw/mihou/nexus/features/command/core/NexusCommandDispatcher.java b/src/main/java/pw/mihou/nexus/features/command/core/NexusCommandDispatcher.java
deleted file mode 100755
index 7241d317..00000000
--- a/src/main/java/pw/mihou/nexus/features/command/core/NexusCommandDispatcher.java
+++ /dev/null
@@ -1,70 +0,0 @@
-package pw.mihou.nexus.features.command.core;
-
-import org.javacord.api.event.interaction.SlashCommandCreateEvent;
-import org.javacord.api.util.logging.ExceptionLogger;
-import pw.mihou.nexus.core.NexusCore;
-import pw.mihou.nexus.core.threadpool.NexusThreadPool;
-import pw.mihou.nexus.features.command.facade.NexusCommandEvent;
-import pw.mihou.nexus.features.command.interceptors.core.NexusCommandInterceptorCore;
-import pw.mihou.nexus.features.command.interceptors.core.NexusMiddlewareGateCore;
-import pw.mihou.nexus.features.messages.core.NexusMessageCore;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class NexusCommandDispatcher {
-
- /**
- * Dispatches one slash command create event of a command onto the given {@link NexusCommandCore}.
- * This performs the necessary middleware handling, dispatching to the listener and afterware handling.
- *
- * This is synchronous by nature except when the event is dispatched to its respective listener and also
- * when the afterwares are executed.
- *
- * @param instance The {@link NexusCommandCore} instance to dispatch the event towards.
- * @param event The {@link SlashCommandCreateEvent} event to dispatch.
- */
- public static void dispatch(NexusCommandCore instance, SlashCommandCreateEvent event) {
- NexusCommandEvent nexusEvent = new NexusCommandEventCore(event, instance);
- List middlewares = new ArrayList<>();
- middlewares.addAll(instance.core.getGlobalMiddlewares());
- middlewares.addAll(instance.middlewares);
-
- List afterwares = new ArrayList<>();
- afterwares.addAll(instance.core.getGlobalAfterwares());
- afterwares.addAll(instance.afterwares);
-
- NexusMiddlewareGateCore middlewareGate = (NexusMiddlewareGateCore) NexusCommandInterceptorCore.interceptWithMany(middlewares, nexusEvent);
-
- if (middlewareGate != null) {
- NexusMessageCore middlewareResponse = ((NexusMessageCore) middlewareGate.response());
- if (middlewareResponse != null) {
- middlewareResponse
- .convertTo(nexusEvent.respondNow())
- .respond()
- .exceptionally(ExceptionLogger.get());
- }
- return;
- }
-
- if (event.getSlashCommandInteraction().getChannel().isEmpty()) {
- NexusCore.logger.error(
- "The channel of a slash command event is somehow not present; this is possibly a change in Discord's side " +
- "and may need to be addressed, please send an issue @ https://github.com/ShindouMihou/Nexus"
- );
- }
-
- NexusThreadPool.executorService.submit(() -> {
- try {
- instance.handler.onEvent(nexusEvent);
- } catch (Throwable throwable) {
- NexusCore.logger.error("An uncaught exception was received by Nexus Command Dispatcher for the " +
- "command " + instance.name + " with the following stacktrace.");
- throwable.printStackTrace();
- }
- });
-
- NexusThreadPool.executorService.submit(() -> NexusCommandInterceptorCore.interceptWithMany(afterwares, nexusEvent));
- }
-
-}
diff --git a/src/main/java/pw/mihou/nexus/features/command/core/NexusCommandDispatcher.kt b/src/main/java/pw/mihou/nexus/features/command/core/NexusCommandDispatcher.kt
new file mode 100755
index 00000000..d73fc09c
--- /dev/null
+++ b/src/main/java/pw/mihou/nexus/features/command/core/NexusCommandDispatcher.kt
@@ -0,0 +1,115 @@
+package pw.mihou.nexus.features.command.core
+
+import org.javacord.api.event.interaction.SlashCommandCreateEvent
+import org.javacord.api.util.logging.ExceptionLogger
+import pw.mihou.nexus.Nexus
+import pw.mihou.nexus.Nexus.globalAfterwares
+import pw.mihou.nexus.Nexus.globalMiddlewares
+import pw.mihou.nexus.Nexus.logger
+import pw.mihou.nexus.features.command.facade.NexusCommandEvent
+import pw.mihou.nexus.features.command.interceptors.core.NexusCommandInterceptorCore
+import pw.mihou.nexus.features.command.interceptors.core.NexusMiddlewareGateCore
+import pw.mihou.nexus.features.command.validation.OptionValidation
+import pw.mihou.nexus.features.command.validation.middleware.OptionValidationMiddleware
+import pw.mihou.nexus.features.command.validation.result.ValidationResult
+import java.time.Instant
+import java.util.concurrent.CompletableFuture
+import java.util.concurrent.atomic.AtomicBoolean
+
+object NexusCommandDispatcher {
+ /**
+ * Dispatches one slash command create event of a command onto the given [NexusCommandCore].
+ * This performs the necessary middleware handling, dispatching to the listener and afterware handling.
+ *
+ * This is synchronous by nature except when the event is dispatched to its respective listener and also
+ * when the afterwares are executed.
+ *
+ * @param instance The [NexusCommandCore] instance to dispatch the event towards.
+ * @param event The [SlashCommandCreateEvent] event to dispatch.
+ */
+ fun dispatch(instance: NexusCommandCore, event: SlashCommandCreateEvent) {
+ val nexusEvent = NexusCommandEventCore(event, instance)
+
+ val middlewares: MutableList = ArrayList()
+ middlewares.add(OptionValidationMiddleware.NAME)
+ middlewares.addAll(globalMiddlewares)
+ middlewares.addAll(instance.middlewares)
+
+ val afterwares: MutableList = ArrayList()
+ afterwares.addAll(globalAfterwares)
+ afterwares.addAll(instance.afterwares)
+
+ var dispatched = false
+
+ try {
+ val middlewareGate: NexusMiddlewareGateCore? = if (Nexus.configuration.interceptors.autoDeferMiddlewareResponses) {
+ val future = CompletableFuture.supplyAsync {
+ NexusCommandInterceptorCore.execute(nexusEvent, NexusCommandInterceptorCore.middlewares(middlewares))
+ }
+ val timeUntil = Instant.now().toEpochMilli() -
+ event.interaction.creationTimestamp.minusMillis(Nexus.configuration.global.autoDeferAfterMilliseconds).toEpochMilli()
+ val deferredTaskRan = AtomicBoolean(false)
+ val task = Nexus.launch.scheduler.launch(timeUntil) {
+ deferredTaskRan.set(true)
+ if (future.isDone) {
+ return@launch
+ }
+ nexusEvent.respondLaterEphemerallyIf(Nexus.configuration.interceptors.autoDeferAsEphemeral)
+ .exceptionally(ExceptionLogger.get())
+ }
+ val gate = future.join()
+ if (!deferredTaskRan.get()) {
+ task.cancel(false)
+ }
+ gate
+ } else {
+ NexusCommandInterceptorCore.execute(nexusEvent, NexusCommandInterceptorCore.middlewares(middlewares))
+ }
+
+ if (middlewareGate != null) {
+ val middlewareResponse = middlewareGate.response()
+ if (middlewareResponse != null) {
+ val updaterFuture = nexusEvent.updater.get()
+ if (updaterFuture != null) {
+ val updater = updaterFuture.join()
+ middlewareResponse.into(updater).update().exceptionally(ExceptionLogger.get())
+ } else {
+ var responder = nexusEvent.respondNow()
+ if (middlewareResponse.ephemeral) {
+ responder = nexusEvent.respondNowEphemerally()
+ }
+ middlewareResponse.into(responder).respond().exceptionally(ExceptionLogger.get())
+ }
+ }
+ return
+ }
+
+ if (event.slashCommandInteraction.channel.isEmpty) {
+ logger.error(
+ "The channel of a slash command event is somehow not present; this is possibly a change in Discord's side " +
+ "and may need to be addressed, please send an issue @ https://github.com/ShindouMihou/Nexus"
+ )
+ }
+
+ dispatched = true
+ Nexus.launcher.launch {
+ try {
+ instance.handler.onEvent(nexusEvent)
+ } catch (throwable: Throwable) {
+ logger.error("An uncaught exception was received by Nexus Command Dispatcher for the " +
+ "command ${instance.name} with the following stacktrace.", throwable)
+ }
+ }
+
+ Nexus.launcher.launch {
+ NexusCommandInterceptorCore.execute(nexusEvent, NexusCommandInterceptorCore.afterwares(afterwares))
+ }
+ } catch (exception: Exception) {
+ logger.error("An uncaught exception occurred within Nexus' dispatcher for command ${instance.name}.", exception)
+ } finally {
+ if (!dispatched) {
+ NexusCommandInterceptorCore.execute(nexusEvent, NexusCommandInterceptorCore.afterwares(afterwares), dispatched = false)
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/pw/mihou/nexus/features/command/core/NexusCommandEventCore.java b/src/main/java/pw/mihou/nexus/features/command/core/NexusCommandEventCore.java
deleted file mode 100755
index dbca6c07..00000000
--- a/src/main/java/pw/mihou/nexus/features/command/core/NexusCommandEventCore.java
+++ /dev/null
@@ -1,42 +0,0 @@
-package pw.mihou.nexus.features.command.core;
-
-import org.javacord.api.event.interaction.SlashCommandCreateEvent;
-import pw.mihou.nexus.features.command.facade.NexusCommand;
-import pw.mihou.nexus.features.command.facade.NexusCommandEvent;
-
-import java.util.HashMap;
-import java.util.Map;
-
-public class NexusCommandEventCore implements NexusCommandEvent {
-
- private final SlashCommandCreateEvent event;
- private final NexusCommand command;
- private final Map store = new HashMap<>();
-
- /**
- * Creates a new Nexus Event Core that is sent along with the Command Interceptors
- * and other sorts of handlers.
- *
- * @param event The base event received from Javacord.
- * @param command The command instance that is used.
- */
- public NexusCommandEventCore(SlashCommandCreateEvent event, NexusCommand command) {
- this.event = event;
- this.command = command;
- }
-
- @Override
- public SlashCommandCreateEvent getBaseEvent() {
- return event;
- }
-
- @Override
- public NexusCommand getCommand() {
- return command;
- }
-
- @Override
- public Map store() {
- return store;
- }
-}
diff --git a/src/main/java/pw/mihou/nexus/features/command/core/NexusCommandEventCore.kt b/src/main/java/pw/mihou/nexus/features/command/core/NexusCommandEventCore.kt
new file mode 100755
index 00000000..0ae6eb7f
--- /dev/null
+++ b/src/main/java/pw/mihou/nexus/features/command/core/NexusCommandEventCore.kt
@@ -0,0 +1,82 @@
+package pw.mihou.nexus.features.command.core
+
+import org.javacord.api.entity.message.MessageFlag
+import org.javacord.api.event.interaction.SlashCommandCreateEvent
+import org.javacord.api.interaction.callback.InteractionOriginalResponseUpdater
+import org.javacord.api.util.logging.ExceptionLogger
+import pw.mihou.nexus.Nexus
+import pw.mihou.nexus.configuration.modules.Cancellable
+import pw.mihou.nexus.features.command.facade.NexusCommand
+import pw.mihou.nexus.features.command.facade.NexusCommandEvent
+import pw.mihou.nexus.features.command.responses.NexusAutoResponse
+import pw.mihou.nexus.features.messages.NexusMessage
+import java.time.Instant
+import java.util.concurrent.CompletableFuture
+import java.util.concurrent.atomic.AtomicBoolean
+import java.util.concurrent.atomic.AtomicReference
+import java.util.function.Function
+
+class NexusCommandEventCore(override val event: SlashCommandCreateEvent, override val command: NexusCommand) : NexusCommandEvent {
+ private val store: MutableMap = HashMap()
+ var updater: AtomicReference?> = AtomicReference(null)
+
+ override fun store(): MutableMap = store
+
+ override fun autoDefer(ephemeral: Boolean, response: Function): CompletableFuture {
+ var task: Cancellable? = null
+ val deferredTaskRan = AtomicBoolean(false)
+ if (updater.get() == null) {
+ val timeUntil = Instant.now().toEpochMilli() - event.interaction.creationTimestamp
+ .minusMillis(Nexus.configuration.global.autoDeferAfterMilliseconds)
+ .toEpochMilli()
+
+ task = Nexus.launch.scheduler.launch(timeUntil) {
+ if (updater.get() == null) {
+ respondLaterEphemerallyIf(ephemeral).exceptionally(ExceptionLogger.get())
+ }
+ deferredTaskRan.set(true)
+ }
+ }
+ val future = CompletableFuture()
+ Nexus.launcher.launch {
+ try {
+ val message = response.apply(null)
+ if (!deferredTaskRan.get() && task != null) {
+ task.cancel(false)
+ }
+ val updater = updater.get()
+ if (updater == null) {
+ val responder = respondNow()
+ if (ephemeral) {
+ responder.setFlags(MessageFlag.EPHEMERAL)
+ }
+ message.into(responder).respond()
+ .thenAccept { r -> future.complete(NexusAutoResponse(r, null)) }
+ .exceptionally { exception ->
+ future.completeExceptionally(exception)
+ return@exceptionally null
+ }
+ } else {
+ val completedUpdater = updater.join()
+ message.into(completedUpdater).update()
+ .thenAccept { r -> future.complete(NexusAutoResponse(null, r)) }
+ .exceptionally { exception ->
+ future.completeExceptionally(exception)
+ return@exceptionally null
+ }
+ }
+ } catch (exception: Exception) {
+ future.completeExceptionally(exception)
+ }
+ }
+ return future
+ }
+
+ override fun respondLater(): CompletableFuture {
+ return updater.updateAndGet { interaction.respondLater() }!!
+ }
+
+ override fun respondLaterEphemerally(): CompletableFuture {
+ return updater.updateAndGet { interaction.respondLater(true) }!!
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/pw/mihou/nexus/features/command/core/NexusMiddlewareEventCore.java b/src/main/java/pw/mihou/nexus/features/command/core/NexusMiddlewareEventCore.java
deleted file mode 100644
index 31811432..00000000
--- a/src/main/java/pw/mihou/nexus/features/command/core/NexusMiddlewareEventCore.java
+++ /dev/null
@@ -1,27 +0,0 @@
-package pw.mihou.nexus.features.command.core;
-
-import org.javacord.api.event.interaction.SlashCommandCreateEvent;
-import pw.mihou.nexus.features.command.facade.NexusCommand;
-import pw.mihou.nexus.features.command.facade.NexusCommandEvent;
-import pw.mihou.nexus.features.command.facade.NexusMiddlewareEvent;
-
-import java.util.Map;
-
-public record NexusMiddlewareEventCore(NexusCommandEvent event) implements NexusMiddlewareEvent {
-
- @Override
- public SlashCommandCreateEvent getBaseEvent() {
- return event.getBaseEvent();
- }
-
- @Override
- public NexusCommand getCommand() {
- return event.getCommand();
- }
-
- @Override
- public Map store() {
- return event.store();
- }
-
-}
diff --git a/src/main/java/pw/mihou/nexus/features/command/core/NexusMiddlewareEventCore.kt b/src/main/java/pw/mihou/nexus/features/command/core/NexusMiddlewareEventCore.kt
new file mode 100644
index 00000000..2989e49b
--- /dev/null
+++ b/src/main/java/pw/mihou/nexus/features/command/core/NexusMiddlewareEventCore.kt
@@ -0,0 +1,28 @@
+package pw.mihou.nexus.features.command.core
+
+import org.javacord.api.event.interaction.SlashCommandCreateEvent
+import pw.mihou.nexus.features.command.facade.NexusCommand
+import pw.mihou.nexus.features.command.facade.NexusCommandEvent
+import pw.mihou.nexus.features.command.facade.NexusMiddlewareEvent
+import pw.mihou.nexus.features.command.interceptors.core.NexusMiddlewareGateCore
+import pw.mihou.nexus.features.command.responses.NexusAutoResponse
+import pw.mihou.nexus.features.messages.NexusMessage
+import java.util.concurrent.CompletableFuture
+import java.util.function.Function
+
+class NexusMiddlewareEventCore(private val _event: NexusCommandEvent, private val gate: NexusMiddlewareGateCore): NexusMiddlewareEvent {
+ override val event: SlashCommandCreateEvent get() = _event.event
+ override val command: NexusCommand get() = _event.command
+ override fun store(): MutableMap = _event.store()
+ override fun autoDefer(ephemeral: Boolean, response: Function): CompletableFuture {
+ return _event.autoDefer(ephemeral, response)
+ }
+
+ override fun next() {
+ gate.next()
+ }
+
+ override fun stop(response: NexusMessage?) {
+ gate.stop(response)
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/pw/mihou/nexus/features/command/facade/NexusCommand.java b/src/main/java/pw/mihou/nexus/features/command/facade/NexusCommand.java
index 5f89077d..fe19fb32 100755
--- a/src/main/java/pw/mihou/nexus/features/command/facade/NexusCommand.java
+++ b/src/main/java/pw/mihou/nexus/features/command/facade/NexusCommand.java
@@ -1,15 +1,69 @@
package pw.mihou.nexus.features.command.facade;
+import kotlin.Pair;
import org.javacord.api.entity.permission.PermissionType;
import org.javacord.api.interaction.*;
-import pw.mihou.nexus.commons.Pair;
+import pw.mihou.nexus.core.exceptions.NoSuchAfterwareException;
+import pw.mihou.nexus.core.exceptions.NoSuchMiddlewareException;
+import pw.mihou.nexus.features.commons.NexusApplicationCommand;
+import pw.mihou.nexus.features.command.interceptors.core.NexusCommandInterceptorCore;
+import pw.mihou.nexus.features.command.validation.OptionValidation;
-import java.time.Duration;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
+import java.util.*;
+import java.util.stream.Collectors;
-public interface NexusCommand {
+public interface NexusCommand extends NexusApplicationCommand {
+
+ long PLACEHOLDER_SERVER_ID = 0L;
+
+ static Map createLocalized(Pair... entries) {
+ return Arrays.stream(entries).collect(Collectors.toMap(Pair::component1, Pair::component2));
+ }
+
+ static List createOptions(SlashCommandOption... options) {
+ return Arrays.stream(options).toList();
+ }
+
+ static List> createValidators(OptionValidation>... validators) {
+ return Arrays.stream(validators).toList();
+ }
+
+ static List createMiddlewares(String... middlewares) {
+ List list = new ArrayList<>();
+ for (String middleware : middlewares) {
+ if (!NexusCommandInterceptorCore.hasMiddleware(middleware)) throw new NoSuchMiddlewareException(middleware);
+ list.add(middleware);
+ }
+ return list;
+ }
+
+ static List createAfterwares(String... afterwares) {
+ List list = new ArrayList<>();
+ for (String afterware : afterwares) {
+ if (!NexusCommandInterceptorCore.hasAfterware(afterware)) throw new NoSuchAfterwareException(afterware);
+ list.add(afterware);
+ }
+ return list;
+ }
+
+ static List with(long... servers) {
+ return Arrays.stream(servers).boxed().toList();
+ }
+
+ static List createDefaultEnabledForPermissions(PermissionType... permissions) {
+ return Arrays.stream(permissions).toList();
+ }
+
+ /**
+ * Gets the unique identifier of the command, this tends to be the command name unless the
+ * command has an override using the {@link pw.mihou.nexus.features.command.annotation.IdentifiableAs} annotation.
+ *
+ * There are many use-cases for this unique identifier such as retrieving the index (application command id) of the
+ * command from the index store to be used for mentioning the command, and some other more.
+ *
+ * @return the unique identifier of the command.
+ */
+ String getUuid();
/**
* Gets the name of the command.
@@ -33,40 +87,50 @@ public interface NexusCommand {
List getOptions();
/**
- * Gets the cooldown of the command.
+ * Gets all the servers that this command is for.
*
- * @return The cooldown of the command.
+ * @return A list of server ids that this command is for.
*/
- Duration getCooldown();
+ List getServerIds();
/**
- * Gets all the servers that this command is for.
+ * Checks whether this command is a server command.
+ *
+ * A server command can be a server that does have an entry on its associated server ids.
*
- * @return A list of server ids that this command is for.
+ * @return Is this a server command.
*/
- List getServerIds();
+ default boolean isServerCommand() {
+ return !getServerIds().isEmpty();
+ }
/**
- * Adds the specified server to the list of servers to
- * register this command with. If {@link pw.mihou.nexus.Nexus} has the
- * autoApplySupportedServersChangesOnCommands option enabled then it
- * will automatically update this change.
+ * Associates this command from the given servers, this can be used to include this command into the command list
+ * of the server after batching updating.
+ *
+ * This does not perform any changes onto Discord.
+ *
+ * If you want to update changes then please use
+ * the {@link pw.mihou.nexus.features.command.synchronizer.NexusSynchronizer#batchUpdate(long)} method
+ * after using this method.
*
- * @param serverIds The server ids to add support for this command.
- * @return {@link NexusCommand} instance for chain-calling methods.
+ * @param serverIds The snowflakes of the servers to disassociate this command from.
+ * @return the current and updated {@link NexusCommand} instance for chain-calling methods.
*/
- NexusCommand addSupportFor(Long... serverIds);
+ NexusCommand associate(Long... serverIds);
/**
- * Removes the specified server to the list of servers to
- * register this command with. If {@link pw.mihou.nexus.Nexus} has the
- * autoApplySupportedServersChangesOnCommands option enabled then it
- * will automatically update this change.
+ * Disassociates this command from the given servers, removing any form of association with
+ * the given servers.
+ *
+ * This does not perform any changes onto Discord. If you want to update changes then please use
+ * the {@link pw.mihou.nexus.features.command.synchronizer.NexusSynchronizer#batchUpdate(long)} method
+ * after using this method.
*
- * @param serverIds The server ids to remove support for this command.
- * @return {@link NexusCommand} instance for chain-calling methods.
+ * @param serverIds The snowflakes of the servers to disassociate this command from.
+ * @return the current and updated {@link NexusCommand} instance for chain-calling methods.
*/
- NexusCommand removeSupportFor(Long... serverIds);
+ NexusCommand disassociate(Long... serverIds);
/**
* Gets a specific custom field that is annotated with {@link pw.mihou.nexus.core.reflective.annotations.Share} from
@@ -109,6 +173,12 @@ public interface NexusCommand {
*/
boolean isDefaultDisabled();
+ /**
+ * Checks whether the command is not-safe-for-work or not.
+ * @return Whether the command is not-safe-for-work or not.
+ */
+ boolean isNsfw();
+
/**
* Gets the permission types required to have this command enabled for that user.
*
@@ -152,19 +222,8 @@ default Optional getNameLocalization(DiscordLocale locale) {
}
/**
- * Gets the server id of the command.
- *
- * @return The server ID of the command.
- */
- @Deprecated
- long getServerId();
-
- /**
- * Transforms this into a slash command builder that can be used to create
- * the slash command yourself, it returns this via a {@link Pair} containing the server id
- * and the builder.
- *
- * @return The server id of the server this is intended (nullable) and the slash command builder.
+ * Transforms this into a slash command builder that can be used to create the slash command.
+ * @return the {@link SlashCommandBuilder}.
*/
default SlashCommandBuilder asSlashCommand() {
SlashCommandBuilder builder = SlashCommand.with(getName().toLowerCase(), getDescription())
@@ -173,6 +232,7 @@ default SlashCommandBuilder asSlashCommand() {
getNameLocalizations().forEach(builder::addNameLocalization);
getDescriptionLocalizations().forEach(builder::addDescriptionLocalization);
+ builder.setNsfw(isNsfw());
if (isDefaultDisabled()) {
builder.setDefaultDisabled();
}
@@ -193,12 +253,10 @@ default SlashCommandBuilder asSlashCommand() {
}
/**
- * Transforms this into a slash command updater that can be used to update
- * the slash command yourself, it returns this via a {@link Pair} containing the server id
- * and the updater.
+ * Transforms this into a slash command updater that can be used to update the slash command yourself.
*
* @param commandId The ID of the command to update.
- * @return The server id of the server this is intended (nullable) and the slash command updater.
+ * @return the {@link SlashCommandUpdater}.
*/
default SlashCommandUpdater asSlashCommandUpdater(long commandId) {
SlashCommandUpdater updater = new SlashCommandUpdater(commandId)
@@ -209,6 +267,7 @@ default SlashCommandUpdater asSlashCommandUpdater(long commandId) {
getNameLocalizations().forEach(updater::addNameLocalization);
getDescriptionLocalizations().forEach(updater::addDescriptionLocalization);
+ // TODO: Add support for `nsfw` update once new version out. Refers to (https://github.com/Javacord/Javacord/pull/1225).
if (isDefaultDisabled()) {
updater.setDefaultDisabled();
}
diff --git a/src/main/java/pw/mihou/nexus/features/command/facade/NexusCommandEvent.java b/src/main/java/pw/mihou/nexus/features/command/facade/NexusCommandEvent.java
deleted file mode 100755
index 96ad0923..00000000
--- a/src/main/java/pw/mihou/nexus/features/command/facade/NexusCommandEvent.java
+++ /dev/null
@@ -1,275 +0,0 @@
-package pw.mihou.nexus.features.command.facade;
-
-import org.javacord.api.DiscordApi;
-import org.javacord.api.entity.channel.ServerTextChannel;
-import org.javacord.api.entity.channel.TextChannel;
-import org.javacord.api.entity.message.MessageFlag;
-import org.javacord.api.entity.server.Server;
-import org.javacord.api.entity.user.User;
-import org.javacord.api.event.interaction.SlashCommandCreateEvent;
-import org.javacord.api.interaction.SlashCommandInteraction;
-import org.javacord.api.interaction.SlashCommandInteractionOption;
-import org.javacord.api.interaction.callback.InteractionImmediateResponseBuilder;
-import org.javacord.api.interaction.callback.InteractionOriginalResponseUpdater;
-import pw.mihou.nexus.Nexus;
-import pw.mihou.nexus.core.managers.NexusShardManager;
-import pw.mihou.nexus.features.command.core.NexusCommandCore;
-
-import java.util.List;
-import java.util.Map;
-import java.util.NoSuchElementException;
-import java.util.Optional;
-import java.util.concurrent.CompletableFuture;
-import java.util.function.Predicate;
-
-public interface NexusCommandEvent {
-
- /**
- * Gets the base event that was received from Javacord. This is usually nothing
- * of use to the end-user.
- *
- * @return The base event that was received.
- */
- SlashCommandCreateEvent getBaseEvent();
-
- /**
- * Gets the interaction received from Javacord.
- *
- * @return The interaction that was received from Javacord.
- */
- default SlashCommandInteraction getInteraction() {
- return getBaseEvent().getSlashCommandInteraction();
- }
-
- /**
- * Gets the user received from Javacord.
- *
- * @return The user that was received from Javacord.
- */
- default User getUser() {
- return getInteraction().getUser();
- }
-
- /**
- * Gets the channel that isn't an optional. This exists primarily because there is no currently possible
- * way for the text channel to be optional in Discord Slash Commands.
- *
- * @return The text channel that is being used.
- */
- default TextChannel getChannel() {
- return getInteraction().getChannel().orElseThrow(() -> new NoSuchElementException(
- "It seems like the text channel is no longer always available. Please create an issue on https://github.com/ShindouMihou/Nexus"
- ));
- }
-
- /**
- * Gets the server instance.
- *
- * @return The server instance.
- */
- default Optional getServer() {
- return getInteraction().getServer();
- }
-
- /**
- * Gets the ID of the server.
- *
- * @return The ID of the server.
- */
- default Optional getServerId() {
- return getInteraction().getServer().map(Server::getId);
- }
-
- /**
- * Gets the ID of the text channel where the command was executed.
- *
- * @return The ID of the text channel where the command was executed.
- */
- default Long getChannelId() {
- return getChannel().getId();
- }
-
- /**
- * Gets the ID of the user that executed the command.
- *
- * @return The ID of the user who executed this command.
- */
- default Long getUserId() {
- return getUser().getId();
- }
-
- /**
- * Gets the text channel as a server text channel.
- *
- * @return The text channel as a server text channel.
- */
- default Optional getServerTextChannel() {
- return getChannel().asServerTextChannel();
- }
- /**
- * Gets the command that was executed.
- *
- * @return The command that was executed.
- */
- NexusCommand getCommand();
-
- /**
- * Gets the {@link Nexus} instance that was in charge
- * of handling this command.
- *
- * @return The Nexus instance that was in charge of this command.
- */
- default Nexus getNexus() {
- return ((NexusCommandCore) getCommand()).core;
- }
-
- /**
- * Gets the Discord API shard that was in charge of this event.
- *
- * @return The Discord API shard that was in charge of this event.
- */
- default DiscordApi getApi() {
- return getBaseEvent().getApi();
- }
-
- /**
- * Gets the {@link NexusShardManager} that is associated with the
- * {@link Nexus} instance.
- *
- * @return The Nexus Shard Manager associated with this Nexus instance.
- */
- default NexusShardManager getShardManager() {
- return getNexus().getShardManager();
- }
-
- /**
- * Gets the options that were brought with this command.
- *
- * @return All the options of this command.
- */
- default List getOptions() {
- return getInteraction().getOptions();
- }
-
- /**
- * Gets the options of a subcommand.
- *
- * @param name The name of the subcommand to search for.
- * @return The options of a subcommand, if present.
- */
- default Optional> getSubcommandOptions(String name) {
- return getInteraction().getOptionByName(name).map(SlashCommandInteractionOption::getOptions);
- }
-
- /**
- * Gets the immediate response for this command.
- *
- * @return The immediate response builder associated with this command.
- */
- default InteractionImmediateResponseBuilder respondNow() {
- return getInteraction().createImmediateResponder();
- }
-
- /**
- * Gets the immediate response builder for this command and adds the
- * {@link MessageFlag#EPHEMERAL} flag ahead of time.
- *
- * @return The immediate response builder associated with this command with the
- * ephemeral flag added.
- */
- default InteractionImmediateResponseBuilder respondNowAsEphemeral() {
- return respondNow().setFlags(MessageFlag.EPHEMERAL);
- }
-
- /**
- * Gets the {@link InteractionOriginalResponseUpdater} associated with this command.
- *
- * @return The {@link InteractionOriginalResponseUpdater}.
- */
- default CompletableFuture respondLater() {
- return getNexus()
- .getResponderRepository()
- .get(getBaseEvent().getInteraction());
- }
-
- /**
- * Gets the {@link InteractionOriginalResponseUpdater} associated with this command with the
- * ephemeral flag attached.
- *
- * @return The {@link InteractionOriginalResponseUpdater} with the ephemeral flag attached.
- */
- default CompletableFuture respondLaterAsEphemeral() {
- return getNexus()
- .getResponderRepository()
- .getEphemereal(getBaseEvent().getInteraction());
- }
-
- /**
- * Gets the {@link InteractionOriginalResponseUpdater} associated with this command with the ephemeral flag
- * attached if the predicate is true.
- *
- * @param predicate The predicate to determine whether to use ephemeral response or not.
- * @return The {@link InteractionOriginalResponseUpdater} for this interaction.
- */
- default CompletableFuture respondLaterAsEphemeralIf(boolean predicate) {
- if (predicate) {
- return respondLaterAsEphemeral();
- }
-
- return respondLater();
- }
-
- /**
- * Gets the {@link InteractionOriginalResponseUpdater} associated with this command with the ephemeral flag
- * attached if the predicate is true.
- *
- * @param predicate The predicate to determine whether to use ephemeral response or not.
- * @return The {@link InteractionOriginalResponseUpdater} for this interaction.
- */
- default CompletableFuture respondLaterAsEphemeralIf(Predicate predicate) {
- return respondLaterAsEphemeralIf(predicate.test(null));
- }
-
- /**
- * Gets the event local store that is used to contain shared fields accessible from middlewares, command and
- * afterwares themselves. You can use this to store data such as whether the command was completed successfully
- * or other related.
- *
- * @return The event-local store from this event.
- */
- Map store();
-
- /**
- * Gets the value of the given key from the {@link NexusCommandEvent#store()} and maps it into the type given
- * if possible, otherwise returns null.
- *
- * @param key The key to get from the {@link NexusCommandEvent#store()}.
- * @param type The type expected of the value.
- * @param The type expected of the value.
- *
- * @return The value mapped with the key in {@link NexusCommandEvent#store()} mapped to the type, otherwise null.
- */
- default T get(String key, Class type) {
- if (!store().containsKey(key))
- return null;
-
- Object object = store().get(key);
-
- if (type.isAssignableFrom(object.getClass())) {
- return type.cast(object);
- }
-
- return null;
- }
-
- /**
- * A short-hand expression for placing a key-value pair to {@link NexusCommandEvent#store()}.
- *
- * @param key The key to insert to the store.
- * @param value The value to insert to the store.
- */
- default void store(String key, Object value) {
- store().put(key, value);
- }
-
-}
diff --git a/src/main/java/pw/mihou/nexus/features/command/facade/NexusCommandEvent.kt b/src/main/java/pw/mihou/nexus/features/command/facade/NexusCommandEvent.kt
new file mode 100755
index 00000000..33a02deb
--- /dev/null
+++ b/src/main/java/pw/mihou/nexus/features/command/facade/NexusCommandEvent.kt
@@ -0,0 +1,185 @@
+package pw.mihou.nexus.features.command.facade
+
+import org.javacord.api.entity.message.MessageFlag
+import org.javacord.api.event.interaction.SlashCommandCreateEvent
+import org.javacord.api.interaction.SlashCommandInteraction
+import org.javacord.api.interaction.callback.InteractionImmediateResponseBuilder
+import org.javacord.api.interaction.callback.InteractionOriginalResponseUpdater
+import org.javacord.api.util.logging.ExceptionLogger
+import pw.mihou.nexus.Nexus
+import pw.mihou.nexus.Nexus.sharding
+import pw.mihou.nexus.features.command.interceptors.core.NexusCommandInterceptorCore.execute
+import pw.mihou.nexus.features.command.interceptors.core.NexusCommandInterceptorCore.middlewares
+import pw.mihou.nexus.features.command.responses.NexusAutoResponse
+import pw.mihou.nexus.features.commons.NexusInteractionEvent
+import pw.mihou.nexus.features.messages.NexusMessage
+import pw.mihou.nexus.sharding.NexusShardingManager
+import java.util.concurrent.CompletableFuture
+import java.util.function.Consumer
+import java.util.function.Function
+import java.util.function.Predicate
+
+@JvmDefaultWithCompatibility
+interface NexusCommandEvent : NexusInteractionEvent {
+
+ /**
+ * Gets the base event that was received from Javacord. This is usually nothing
+ * of use to the end-user.
+ *
+ * @return The base event that was received.
+ * @see NexusCommandEvent.event
+ */
+ @get:Deprecated("Standardized methods across the framework.", ReplaceWith("getEvent()"))
+ val baseEvent: SlashCommandCreateEvent get() = event
+
+ override val interaction: SlashCommandInteraction get() = event.slashCommandInteraction
+ override val event: SlashCommandCreateEvent
+
+ /**
+ * Gets the command that was executed.
+ *
+ * @return The command that was executed.
+ */
+ val command: NexusCommand
+
+ /**
+ * Gets the [NexusShardingManager] that is associated with the [Nexus] instance.
+ *
+ * @return The Nexus Shard Manager associated with this Nexus instance.
+ */
+ val shardManager: NexusShardingManager get() = sharding
+
+ /**
+ * Gets the immediate response builder for this command and adds the
+ * [MessageFlag.EPHEMERAL] flag ahead of time.
+ *
+ * @return The immediate response builder associated with this command with the
+ * ephemeral flag added.
+ * @see NexusCommandEvent.respondNowEphemerally
+ */
+ @Deprecated("Standardized methods across the framework.", ReplaceWith("respondNowEphemerally()"))
+ fun respondNowAsEphemeral(): InteractionImmediateResponseBuilder {
+ return respondNowEphemerally()
+ }
+
+ /**
+ * Gets the [InteractionOriginalResponseUpdater] associated with this command with the
+ * ephemeral flag attached.
+ *
+ * @return The [InteractionOriginalResponseUpdater] with the ephemeral flag attached.
+ * @see NexusCommandEvent.respondLaterEphemerally
+ */
+ @Deprecated("Standardized methods across the framework.", ReplaceWith("respondLaterEphemerally()"))
+ fun respondLaterAsEphemeral(): CompletableFuture