Configuring your bot
Standardizing the way to configure a bot is one of the primary goals of Botrino. This section will cover the configuration part more in detail, how to access the values of the configuration file in your code, and how to add your own configuration entries for your application.
The configuration JSON
The configuration is a JSON object, by default located in a config.json
in the runtime directory, each field at the
root of the object corresponds to one entry and maps to one class in the Java code.
The ConfigContainer
In order to access the values of the configuration in the Java code, Botrino exposes the object ConfigContainer
as a
service that you can inject in your own code. An example below:
package com.example.myproject;
import botrino.api.config.ConfigContainer;
import botrino.api.config.object.BotConfig;
import com.github.alex1304.rdi.finder.annotation.RdiFactory;
import com.github.alex1304.rdi.finder.annotation.RdiService;
@RdiService
public final class SomeService {
private final BotConfig botConfig;
@RdiFactory
public SomeService(ConfigContainer configContainer) {
this.botConfig = configContainer.get(BotConfig.class);
}
}
The ConfigContainer#get(Class)
method is what allows you to access the entries of the JSON config inside your code.
Built-in configuration entries
Botrino comes with a few configuration entries by default. Here is the list of them below for reference.
The bot
entry
This entry is where you input the bot information (token, presence, intents, etc).
JSON structure for bot
:
Field | Type | Description | Required? |
---|---|---|---|
token | string | the token of the bot, generated in the Discord Developer portal | Yes |
presence | object | the presence of the bot in Discord | No, defaults to {"status":"online"} |
enabled_intents | integer | the Gateway Intents to enable | No, defaults to 32509 (all non-privileged intents) |
JSON structure for presence
:
Field | Type | Description | Required? |
---|---|---|---|
status | string | one of "online", "idle", "do_not_disturb", "invisible" | No, defaults to "online" |
activity_type | string | one of "playing", "watching", "listening", "streaming" | No |
activity_text | string | the text to display in the presence activity of the bot | No |
streaming_url | string | the streaming URL, only applicable if "streaming" is set as activity_text | No, defaults to http://127.0.0.1 |
Example:
{
"bot": {
"token": "yourTokenHere",
"presence": {
"status": "online",
"activity_type": "playing",
"activity_text": "Hello world!"
},
"enabled_intents": 32509
}
}
The corresponding class in the Java code is botrino.api.config.object.BotConfig
, accessed
via ConfigContainer.get(BotConfig.class)
.
Adding your own configuration entries
The configuration JSON can of course be extended with more entries to include your own parameters.
Creating the configuration object
First step is to create a POJO like this:
package com.example.myproject;
import botrino.api.annotation.ConfigEntry;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
@JsonDeserialize
@ConfigEntry("my_config")
public final class MyConfig {
private String myProperty;
private long myValue;
public String getMyProperty() {
return myProperty;
}
@JsonProperty("my_property")
public void setMyProperty(String myProperty) {
this.myProperty = myProperty;
}
public long getMyValue() {
return myValue;
}
@JsonProperty("my_value")
public void setMyValue(long myValue) {
this.myValue = myValue;
}
}
- The
@JsonDeserialize
annotation is to indicate that this class is intended for being constructed from a JSON input, processed by the Jackson library. - The
@ConfigEntry
annotation allows Botrino to recognize it as a configuration object to be registered in theConfigContainer
, and to indicate the name of the field in the configuration file.
Adding the entry in the JSON file
Once you've created the object, you can add the following in your config.json
:
{
"bot": {
...
},
"i18n": {
...
},
"my_config": {
"my_property": "hello!!!",
"my_value": 42
}
}
The name of the root field in the JSON must match with the name given in the @ConfigEntry
annotation.
Using the configuration object
To test this, we can create a sample service injecting the ConfigContainer
:
package com.example.myproject;
import botrino.api.config.ConfigContainer;
import com.github.alex1304.rdi.finder.annotation.RdiFactory;
import com.github.alex1304.rdi.finder.annotation.RdiService;
import reactor.util.Logger;
import reactor.util.Loggers;
@RdiService
public final class SampleService {
private static final Logger LOGGER = Loggers.getLogger(SampleService.class);
@RdiFactory
public SampleService(ConfigContainer configContainer) {
var myConfig = configContainer.get(MyConfig.class);
LOGGER.info("My property = {}, my value = {}", myConfig.getMyProperty(), myConfig.getMyValue());
}
}
When running, it should give the following output:
00:16:42.193 [main] DEBUG botrino.api.Botrino - Discovered config entry com.example.myproject.MyConfig
00:16:42.468 [main] INFO com.example.myproject.SampleService - My property = hello!!!, my value = 42
Customizing the JSON source
It is possible to override the behavior of Botrino when loading the configuration by implementing the ConfigReader
interface. This interface has two methods, none of them are required to be implemented:
String loadConfigJson(Path botDirectory) throws IOException
: Allows to customize the way the configuration file is loaded. It is useful if you want to load the configuration from a file that is located at a different path or that has a different name than "config.json". You can even ignore thebotDirectory
parameter and load the JSON from a different source, or directly return a hard-coded JSON string for testing purposes for example. Note that this method throwsIOException
and that the return type is not reactive: indeed, this method is ran by Botrino on the main thread at the very start of the program, as such it does not need to be (and shouldn't be) asynchronous. This method is not required to be implemented: it has a default implementation that will simply read the JSON string from a file namedconfig.json
at the root ofbotDirectory
.ObjectMapper createConfigObjectMapper()
: Allows to customize the JacksonObjectMapper
instance used to parse the JSON string. You can for example register extra modules and deserializers. This method is not required to be implemented: by default it will create anObjectMapper
with only theJdk8Module
registered (allows to recognize types such asjava.util.Optional
).
If no ConfigReader
implementation is found in your module, it will use a default one which can be recreated like this:
package com.example.myproject;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import reactor.core.publisher.Mono;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
public final class DefaultConfigReader implements ConfigReader {
@Override
public String loadConfigJson(Path botDirectory) throws IOException {
return Files.readString(botDirectory.resolve("config.json"));
}
@Override
public ObjectMapper createConfigObjectMapper() {
return new ObjectMapper().registerModule(new Jdk8Module());
}
}
- The implementation class must have a no-arg constructor.
- If more than one implementation of
ConfigReader
are found, it will result in an error as it is impossible to determine which one to use. If you don't want to remove the extra implementation(s), you can mark one of them with the@Primary
annotation to lift the ambiguity. You may alternatively use the@Exclude
annotation if you don't want one implementation to be picked up by Botrino.