Releases: ShindouMihou/Nexus
v1.0.0-beta.07
Changes
- BREAKING Adds additional methods to index store to support getting mention tags. (commit)
Full Changelog: v1.0.0-beta.06...v1.0.0-beta.07
v1.0.0-beta.06
Full Changelog: v1.0.0-beta.05...v1.0.0-beta.06
v1.0.0-beta.05
What's Changed
- Adds a basic subcommand router and copying option validators. by @ShindouMihou in #12
Full Changelog: v1.0.0-beta.02...v1.0.0-beta.05
v1.0.0-beta.02
Global and Local Inheritance (#11) * feat: local property inheritance * feat: global inheritance * fix: inheritance with no reference * fix: inheritance with private constructor * fix: additional changes required for local inheritance * fix: make use of kotlin's instance variable
v1.0.0-alpha6
What's Changed
- Fixes a very critical bug where the synchronizer wouldn't complete nor throw exceptions, this was because the futures were being ignored by the synchronizer.
- Adds support for anonymous command interceptors and inline named interceptors. (examples can be found on
interceptors
) - Adds support for scheduled destruction with button removals or other executions on Nexus Paginator Instances. (examples can be found on
PoemCommand
). - Adds uncaught exception handler for areas like the command dispatcher, feather dispatcher and the EngineX dispatcher to ensure no exceptions around those areas are being uncaught.
- EngineX is now publicly exposed in the Nexus instance for public usage when needed.
- Adds
getServerBy(long id)
method in NexusShardManager.
Full Changelog: v1.0.0-alpha5...v1.0.0-alpha6
v1.0.0-alpha5
This adds official support for Javacord 3.5.0 with the following changes:
NexusObserver
which was the legacy and deprecated slash command synchronizer for Nexus is now removed, please use yournexus.getSynchronizer()
instead which is more configurable and faster.- All calls to
InteractionCallbackFlag
is now replaced withMessageFlag
- Adds support for the new slash command version 2.0 permissions, read below for more information.
- Adds support for slash command localization, read below for more information.
⚔️ Slash Command Permissions V2
Nexus supports most if not all the new APIs from Javacord's slash command permissions with the following new fields added to the command schema (default values are as specified below):
const val enabledInDms: Boolean = true;
const val defaultDisabled: Boolean = false;
val defaultEnabledForPermissions: List<PermissionType> = emptyList();
- enabledInDms: Enables or disables the command on user's private messages, setting this to false will hide this command from the private messages.
- defaultDisabled: Disables the command globally, it will make this command hidden from all users other than the admin of the server.
- defaultEnabledForPermissions: Enables this command by default for people with the given permissions, can be overriden by the server owners or administrators.
(there is also one field that was added which is defaultEnabledForEveryone
but that's sort of just there for no reason and is ignored once defaultDisabled
is set to true).
🌏 Slash Command Localizations
A basic support for slash command localization has been implemented, note that this is the bare basic support since there are no plans so far to go beyond the APIs provided by Javacord. This adds the following new fields to the command schema (default values are as specified below):
val nameLocalizations: Map<DiscordLocale, String> = emptyMap()
val descriptionLocalizations: Map<DiscordLocale, String> = emptyMap()
- nameLocalizations: Adds a new localization value for the name, this should be paired with a localized description as well.
- descriptionLocalizations: Adds a new localization value for the description, this should be paired with a localized name as well.
You can access these values as well during command event execution, for example, we want to get the description value or the name value of a locale during command execution (for whatever reason):
override fun onEvent(event: NexusCommandEvent) {
const name = event.command.getNameLocalization(DiscordLocale.JAPANESE);
}
📦 Installation with Jitpack
You can find all the dependency managers installation on the link here: https://jitpack.io/#pw.mihou/Nexus/1.0.0-alpha5
v1.0.0-alpha4.1
This introduces an entire new paginator that is lightweight, faster, and barebones compared to the plain paginator which will allow pagination that queries the database on the next page, previous page, etc.
Warning
Before proceeding, I would like to warn you that all the code samples here are written in Kotlin since I am more familiar and comfortable with Kotlin now compared to Java. It should still be easy to understand despite that.
🪶 Feather Paging
Feather Paging is the name of the lightweight paginator of Nexus which is more barebones and provides minor abstractions that many might like. I always found the normal paginator a bit too limiting and bulky and wanted something that was light and allowed for more possibilities and this is what I came up with.
There was also the issue where the paginators won't work anymore after restarting the bot application which was because all the data was being stored in the memory and also a potential issue of blockage in the memory because the chances of the data not being cleared were decently high until you destroyed the paginator instances. This solves all those problems.
To summarize the differences:
- Feather doesn't create the buttons for you.
- Feather doesn't create events such as
FeatherNextListener
or anything similar. Instead, we are opting for a singular listener that contains the abstractions of Nexus such asgetUserId
, etc. with additional methods that use the Paging API. (NexusFeatherView
). - Feather paginators don't break apart after every restart but instead continue functioning. (this is a key difference but this also requires your code to be able to query new data, etc.)
- Feather is incredibly lightweight.
- Feather is almost barebones.
- Feather has no extra functionality other than to assist with pagination, no fancy stuff.
🔑 Key Terminologies
To understand how Feather works, let's understand the key terminologies that Feather uses:
key
: A key is basically a unique identifier in a sense of the item being paginated. If you are using a bucket pattern (which is the most common and RECOMMENDED method of paging items in MongoDB) then this key will be either the last item's or the next item's key. If you are using skip-limit or offset then this will be the page.type
: A "sorta-unique" name for the Feather View that will be handling the events for this paginator. For example, a paginator for inventory can have something likeinventory.pager
.pager
: This contains both thekey
andtype
which can be accessed viaevent.pager
in Kotlin orevent.getPager()
in Java.action
: The action performed by the user.
📖 Understanding Feather
After the terminologies come the concept of Feather and how it maintains availability even after restarts. To understand truly how Feather works, it abuses the customId
field of Discord buttons and tries to store as much data in the field as possible. A sample of a customId
made by Feather would look like this:
your.type.here[$;your_key[$;action
All the data are delimited with a little [$;
to ensure that your key won't be caught up accidentally while splitting into three parts. This is also a vital part that you have to ensure since this will cause issues if your key actually contains [$;
. After splitting, it takes the data and distributes them to the event. That's basically the entirety of Feather.
Note
Key notes from above:
- Ensure the key doesn't include the following sequence `[$;`` since that is the delimiter of Feather.
- Feather simply adds data into your buttons custom id in the schema of
type[$;key[$;action
.
📲 Actually Paging
After understanding the concept of Feather, let's start paging!
To create a paginator, you first have to create a Feather View which can be done via:
val pager = NexusFeatherPaging.register("your.type.here", YourNexusFeatherViewHere)
object YourNexusFeatherViewHere: NexusFeatherView {
override fun onEvent(event: NexusFeatherViewEvent) {}
}
In this part, you are creating the handler that will be dispatched every time a button that matches the type and schema of Feather is clicked. After creating the View, you need to create a pager before you can actually use Feather.
val items = ...
val pager = NexusFeatherPaging.pager(initialKey = items.last()._id.toString() + ";{${event.userId}", type = "inventory.pager")
The above is a sample of a project that I am working on which uses Feather. The above simply stores the last item's _id
value (the unique id in MongoDB) and the user who invoked the command (delimited with ;{
to prevent collision with Feather). Adding the user field is completely optional but not adding it will prevent you from knowing who originally invoked the pagination.
Note
Some key notes from the above.
- The code sample includes the user id to identify who the invoker of the command is, this is completely optional if you want to allow other users to use the paginator as well.
This pager contains vital information such as the initial key and the type of the paginator. You can then use the pager instance that was created to actually create the button custom ids. An example that I am using on a project is:
updater.addEmbed(InventoryView.embed(event.user, items)).addComponents(
ActionRow.of(
pager.makeWithCurrentKey(action = "next").setLabel("Next (❀❛ ֊ ❛„)♡").setStyle(ButtonStyle.SECONDARY).build(),
pager.makeWithCurrentKey(action = "delete").setEmoji("").setLabel("🗑️").setStyle(ButtonStyle.PRIMARY).build(),
pager.makeWithCurrentKey(action = "reverse").setLabel("૮₍ ˶•⤙•˶ ₎ა Previous").setStyle(ButtonStyle.SECONDARY).build()
)
) .update()
As you can see, we are creating the buttons with the method pager.makeWithCurrentKey(action)
which creates a ButtonBuilder
with the customId
field already pre-filled with the generated custom id. The action
field can be anything. You can then send that message and it will work as intended and show the buttons on Discord.
Now comes the fun part, actually performing the pagination. To do this, let's head back to our Feather View and actually write out the code that we want.
object InventoryView: NexusFeatherView {
override fun onEvent(event: NexusFeatherViewEvent) {
// We are acknowledging the event before actually performing anything since we are just editing the message and don't need to respond later or anything.
exceptionally(event.interaction.acknowledge())
// This is how we acquire the key and invoker, remember what I mentioned earlier.
val key = event.pager.key.substringBefore(";{")
val invoker = event.pager.key.substringAfter(";{")
// This is done to prevent other users other than the invoker from using the paginator.
if (event.userId != invoker.toLong()) return
if (event.action == "delete") {
event.message.delete()
return
}
MyThreadPool.submit {
var items: List<UserItem> = emptyList()
when (event.action) {
"next" -> {
items = ItemDatabase.findAfter(event.userId, key, 20).join()
}
"reverse" -> {
items = ItemDatabase.findBefore(event.userId, key, 20).join()
}
}
if (items.isEmpty()) return@submit
exceptionally(
event.message.createUpdater()
.removeAllEmbeds()
.removeAllComponents()
.addEmbed(embed(event.user, items))
.addComponents(ActionRow.of(makeFeatherComponents(event, items.last()._id.toString() + ";{$invoker"))) // Read more below
.applyChanges()
)
}
}
fun embed(user: User, items: List<UserItem>): EmbedBuilder {
// ... Imagine code that builds the embed builder here
}
}
You may have noticed the makeFeatherComponents
method and may be confused. To summarize the function of that method, it's a custom utility method of mine that copies all the buttons of the first action row and updates the custom id.
fun makeFeatherComponents(event: NexusFeatherViewEvent, newKey: String): List<Button> {
return event.message.components.map {
it.asActionRow().orElseThrow().components.map { component ->
component.asButton().orElseThrow()
}
}.map {
it.map { button ->
val buttonBuilder = event.pager.makeWith(
newKey,
button.customId.orElseThrow().substringAfterLast("[$;")
).setStyle(button.style)
if (button.label.isPresent) buttonBuilder.setLabel(button.label.get())
if (button.emoji.isPresent) buttonBuilder.setEmoji(button.emoji.get())
if (button.url.isPresent) buttonBuilder.setLabel(button.url.get())
if (button.isDisabled.isPresent) buttonBuilder.setDisabled(button.isDisabled.get())
buttonBuilder.build()
}
}.first()
}
In a sense, it shows how barebones the Feather API really is. It doesn't handle any of the buttons or anything even, it simply provides a very TINY abstraction that helps with pagination. You can then run the code and BAM paginator! It's as simple as that.
v1.0.0-alpha4.01
- Patches an issue where the internal command manager becomes unable to map global commands to their respective Nexus counterparts, this was caused by global commands being able to have
interaction.getServer
present which Nexus didn't take into consideration.
v1.0.0-alpha4
This introduces several deprecations and performance improvements with marginal memory reduction to the framework:
NexusAttach
is now deprecated for removal since the annotation wasn't as much used as originally intended, the behavior has been replaced for methods such asNexus#listenOne(Object)
,Nexus#listenMany(Object...)
withNexus#defineOne(Object)
andNexus#defineMany(Object...)
introduced to replace the behavior of unattached commands.Nexus#createCommandFrom(Object)
has been deprecated for removal in favor of methods such asNexus#listenOne(Object)
,Nexus#listenMany(Object...)
withNexus#defineOne(Object)
andNexus#defineMany(Object...)
introduced to replace the behavior of unattached commands.NexusBaseCommandImplementation
(internal class) has been renamed toNexusCommandDispatcher
and no longer requires to be created with the command instance. This was seen as one of the minor fault-lines of a potential memory increase when used in major bots and also a performance bottleneck since it had to create a new implementation class for each new event.- Nexus will no longer throw an exception when
SlashCommandCreateEvent#getChannel()
returns empty but instead throws a warning, please note that this will definitely cause events that useNexusEvent#getChannel
to tear apart if Discord ever somehow makes that method possible to be empty. (hopeful that it won't happen). - The reflection engine has been refactored to no longer create instances using reflection and instead uses the non-reflection method to create a new instance of
NexusCommandCore
which should cause generation time to be so much faster. The engine has also been refactored to no longer create two instances during generation (one is the instance that was always returned after all other stuff, and the other served as more of a reference for all the default values) which caused a decent amount of call stack and also some performance bottlenecks that shouldn't be in any way visible. - Attaching commands are now much faster as we are no longer using reflection to call the attach method but instead are now calling directly from the method itself without any reflection. (Reflection is known to be slow when invoking methods).
v1.0.0-alpha3.11
This patch introduces a short solution of using NexusSynchronizer#batchUpdate
with a DiscordApi shard as a parameter instead of the total shards.