-
Notifications
You must be signed in to change notification settings - Fork 0
Custom Decorators
As Halpbot is built using Hartshorn, it's very easy to override the built-in decorators or to create entirely new decorators.
It is possible to override the built-in decorators by binding a custom implementation with a higher priority than the default implementation. All Halpbot default implementations use a priority of -1, so any priority of 0+ will be used preferentially when constructing the decorator. It's important to note that all decorators must have a @Bound constructor of an ActionInvokable<C> and the decorator annotation.
Example: Overriding the @Log decorator
The basic format any custom-decorators is like so:
Show Imports
import org.dockbox.hartshorn.core.annotations.inject.Binds;
import org.dockbox.hartshorn.core.annotations.inject.Bound;
import nz.pumbas.halpbot.actions.invokable.ActionInvokable;
import nz.pumbas.halpbot.actions.invokable.InvocationContext;// @Binds indicates that this implementation defines the LogDecorator and should be used in the factory rather than the default
// LogDecorator due to the higher priority
@Binds(value = LogDecorator.class, priority = 0)
// All Decorators take in a generic parameter 'C extends InvocationContext' as they can be used by any action,
// each of which have their own InvocationContext
public class CustomLogDecorator<C extends InvocationContext> extends LogDecorator<C>
{
// @Bound specifies that this constructor should be used by the factory to create this decorator
@Bound
public CustomLogDecorator(ActionInvokable<C> actionInvokable, Log log) {
super(actionInvokable, log);
}
}In this custom decorator, we can then override any of the ActionInvokable<C> methods. In this case, let's override the Invoke(C invocationContext) method and make it log the exact message sent (Assuming that the action was invoked using a MessageReceivedEvent).
TIP: In IntelliJ it's possible to bring up all the overridable methods using CTRL + O (⌃ O for MacOS)
Note: Actions aren't limited to message commands, such as buttons, so it's important to check the instance of the event rather than just calling
HalpbotEvent#event(Class<?>)unless you're entirely certain.
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import org.dockbox.hartshorn.core.domain.Exceptional;
import nz.pumbas.halpbot.events.HalpbotEvent;
//...
@Override
public <R> Exceptional<R> invoke(C invocationContext) {
HalpbotEvent halpbotEvent = invocationContext.halpbotEvent();
// If the invocation context is created internally - Rather than via an event listener,
// the halpbot event may be null
if (halpbotEvent != null && halpbotEvent.rawEvent() instanceof MessageReceivedEvent messageEvent) {
this.logLevel().log(invocationContext.applicationContext(),
"[%s] %s".formatted(messageEvent.getClass().getSimpleName(), messageEvent.getMessage().getContentRaw()));
}
// Invoke the old decorator like normally
return super.invoke(invocationContext);
}With this new decorator, when this command is invoked, you get the following logged in the console:
@Log(LogLevel.INFO)
@Command(description = "Tests the @Log decorator")
public String log() {
return "This command is logged when it is invoked";
}16:08:51.008 [ inWS-ReadThread] @ 13236 -> n.p.halpbot.decorators.log.LogLevel INFO - [MessageReceivedEvent] $log
16:08:51.009 [ inWS-ReadThread] @ 13236 -> n.p.halpbot.decorators.log.LogLevel INFO - [Bot Testing][general] pumbas600#7051 has invoked the action HalpbotCommands#log()
If you wanted to avoid invoking the overridden decorator (Instead calling the actual action itself - or the next decorator if there are multiple on the action) then you can do this by calling it from the decorated ActionInvokable<C> like so:
return this.actionInvokable().invoke(invocationContext);Sometimes, like in @Cooldown and @Permission, you don't want to invoke the action at all if certain conditions aren't met, however, you still want to display a message to the user informing them the reason why the action wasn't performed. In these situations, you can return an Exceptional containing an ExplainedException with any object and it will be displayed temporarily.
For example, consider this snippet from the PermissionDecorator:
@Override
public <R> Exceptional<R> invoke(C invocationContext) {
HalpbotEvent event = invocationContext.halpbotEvent();
if (event == null || this.permissionService.hasPermissions(event.user().getIdLong(), this.permissions)) {
return super.invoke(invocationContext);
}
return Exceptional.of(new ExplainedException("You do not have permission to use this command"));
}To create your own decorators, you need 3 things:
- An annotation, annotated with
@Decorator. - A decorator that extends
ActionInvokableDecoratorand has a@Boundconstructor which takes in anActionInvokableand the annotation you just created. - A decorator factory that is used to dynamically instantiate your decorator.
- Built-in Commands
- @Command Parameters
- Arguments
- Annotations
- Custom Objects
- Custom TypeParsers
- Slash Commands - W.I.P.
- Pagination - W.I.P