A clean, modular Discord bot API framework built with Java and JDA. Orion-API provides the core interfaces and contracts for building scalable Discord bots with a plugin-like module system, comprehensive permission management, and hot-reloadable components.
Note: This is the API layer providing interfaces and contracts. For a complete implementation, see Orion-Core.
- Modular Architecture: Clean interfaces for plugin-like module system
- Command System: Interfaces for slash commands with sub-command support
- Event System: Clean event handling contracts
- Configuration Management: Interfaces for YAML-based configuration
- Permission System: Comprehensive permission management interfaces
- Clean Contracts: Well-defined interfaces for all components
- Flexible Implementation: Multiple implementations possible
- Configuration: Flexible configuration system interfaces
- Architecture
- Installation
- Core Concepts
- Module Development
- API Reference
- Examples
- Implementations
- Contributing
- License
Orion-API follows a clean architecture pattern with pure interface definitions:
fr.orion.api/ # Pure API interfaces
βββ command/ # Command system interfaces
β βββ Command.java
β βββ CommandRegistry.java
β βββ ParentCommand.java
βββ config/ # Configuration interfaces
β βββ ModuleConfig.java
β βββ YamlModuleConfig.java
βββ event/ # Event system interfaces
β βββ EventRegistry.java
βββ module/ # Module system interfaces
β βββ Module.java
β βββ AbstractModule.java
β βββ ModuleManager.java
β βββ ModuleDescriptor.java
β βββ loader/
βββ permission/ # Permission system interfaces
β βββ PermissionManager.java
β βββ PermissionNode.java
β βββ impl/ # Default implementations
β βββ YamlPermissionManager.java
βββ interfaction/ # UI interaction helpers
β βββ EmbedTemplate.java
β βββ ConfirmationSystem.java
βββ Bot.java # Main bot interface
- Interface Segregation: Small, focused interfaces
- Dependency Inversion: Depend on abstractions, not concretions
- Open/Closed: Open for extension, closed for modification
- Single Responsibility: Each interface has one clear purpose
Add to your build.gradle:
repositories {
mavenCentral()
maven { url 'https://jitpack.io' }
}
dependencies {
implementation("com.github.Arinonia:orion-api:v0.1.0-beta")
implementation("net.dv8tion:JDA:5.5.1")
implementation("org.yaml:snakeyaml:2.4")
implementation("ch.qos.logback:logback-classic:1.5.13")
}Add to your pom.xml:
<repositories>
<repository>
<id>jitpack</id>
<url>https://jitpack.io</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>com.github.Arinonia</groupId>
<artifactId>orion-api</artifactId>
<version>v0.1.0-beta</version>
</dependency>
<dependency>
<groupId>net.dv8tion</groupId>
<artifactId>JDA</artifactId>
<version>5.5.1</version>
</dependency>
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>2.4</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.5.13</version>
</dependency>
</dependencies>The main entry point providing access to all subsystems:
public interface Bot {
CommandRegistry getCommandRegistry();
EventRegistry getEventRegistry();
JDA getJDA();
ModuleManager getModuleManager();
PermissionManager getPermissionManager();
}Modules are the building blocks of functionality:
public interface Module {
void onEnable();
void onDisable();
boolean isEnabled();
ModuleConfig getConfig();
boolean hasPermission(Member member, String permission);
}Granular permission control with wildcards:
public interface PermissionManager {
boolean hasPermission(Member member, String permission);
void addUserPermission(String userId, String permission);
void addRolePermission(String roleId, String permission);
Set<String> getEffectivePermissions(Member member);
}Extend the provided AbstractModule class:
public class MyModule extends AbstractModule {
@Override
public void onEnable() {
// Register commands
registerCommand(new MyCommand());
// Register event listeners
registerListener(new MyEventListener());
getLogger().info("MyModule enabled!");
}
@Override
public void onDisable() {
getLogger().info("MyModule disabled!");
}
}Create module.yml in your resources:
id: "my_module"
name: "My Awesome Module"
version: "1.0.0"
main: "com.example.MyModule"
description: "A sample module for Orion"
author: "YourName"
dependencies: []
softDependencies: []public class MyCommand extends ParentCommand {
public MyCommand(MyModule module) {
registerSubcommand("test", "Test command",
null,
new SubcommandHandler() {
@Override
public void execute(SlashCommandInteractionEvent event) {
// Check permission using the API
if (!module.hasPermission(event.getMember(), "test")) {
event.reply("β Insufficient permissions").setEphemeral(true).queue();
return;
}
event.reply("β
Test successful!").queue();
}
@Override
public SubcommandData getSubcommandData() {
return new SubcommandData("test", "Test command");
}
}
);
}
@Override
public String getName() { return "mycommand"; }
@Override
public String getDescription() { return "My custom command"; }
}public class MyEventListener extends ListenerAdapter {
private final MyModule module;
public MyEventListener(MyModule module) {
this.module = module;
}
@Override
public void onGuildMemberJoin(GuildMemberJoinEvent event) {
// Use module's logger
module.getLogger().info("New member joined: {}", event.getUser().getAsTag());
// Access configuration
String welcomeMessage = module.getConfig().getString("welcomeMessage");
// Send welcome message
TextChannel channel = event.getGuild().getTextChannelById("CHANNEL_ID");
if (channel != null) {
channel.sendMessage(welcomeMessage + " " + event.getMember().getAsMention()).queue();
}
}
}The API defines a flexible permission system following the pattern: module.action
my_module.test- Specific action permissionmy_module.*- All permissions for the module*- Global administrator access
public interface PermissionManager {
// Check permissions
boolean hasPermission(Member member, String permission);
boolean hasPermission(User user, String permission);
boolean hasPermission(Role role, String permission);
// Manage user permissions
void addUserPermission(String userId, String permission);
void removeUserPermission(String userId, String permission);
Set<String> getUserPermissions(String userId);
// Manage role permissions
void addRolePermission(String roleId, String permission);
void removeRolePermission(String roleId, String permission);
Set<String> getRolePermissions(String roleId);
// Utility methods
Set<String> getEffectivePermissions(Member member);
void clearUserPermissions(String userId);
void clearRolePermissions(String roleId);
}// In your module - automatic prefix with module ID
if (!hasPermission(member, "action")) {
// Permission denied - checks "my_module.action"
}
// Cross-module permission check - full permission string
if (getPermissionManager().hasPermission(member, "other_module.action")) {
// Advanced features
}
// Check wildcard permissions
if (getPermissionManager().hasPermission(member, "my_module.*")) {
// User has all module permissions
}public class PermissionNode {
public PermissionNode(String permission);
public boolean matches(String requiredPermission);
public boolean isWildcard();
public String getModule();
}
// Usage
PermissionNode node = new PermissionNode("my_module.*");
boolean matches = node.matches("my_module.test"); // trueThe API now includes a production-ready YAML-based implementation of PermissionManager:
import fr.orion.api.permission.impl.YamlPermissionManager;
// Create the permission manager
Path dataDir = Paths.get("data");
PermissionManager permissionManager = new YamlPermissionManager(dataDir);
// Add permissions to users
permissionManager.addUserPermission("123456789", "moderation.kick");
permissionManager.addUserPermission("123456789", "moderation.ban");
// Add permissions to roles
permissionManager.addRolePermission("987654321", "music.*");
// Batch operations for better performance
YamlPermissionManager yamlPM = (YamlPermissionManager) permissionManager;
yamlPM.addUserPermissions("123456789", List.of(
"economy.balance",
"economy.transfer",
"economy.shop"
));
// Check permissions
if (permissionManager.hasPermission(member, "moderation.kick")) {
// User has permission through direct user permission or role
}
// Get all effective permissions
Set<String> allPerms = permissionManager.getEffectivePermissions(member);Storage format (permissions.yml):
users:
"123456789":
- "moderation.kick"
- "moderation.ban"
roles:
"987654321":
- "music.*"
- "economy.balance"public interface Bot {
CommandRegistry getCommandRegistry();
EventRegistry getEventRegistry();
JDA getJDA();
ModuleManager getModuleManager();
PermissionManager getPermissionManager();
}public interface Module {
void onEnable();
void onDisable();
boolean isEnabled();
ModuleConfig getConfig();
boolean hasPermission(Member member, String permission);
}public interface PermissionManager {
boolean hasPermission(Member member, String permission);
void addUserPermission(String userId, String permission);
void addRolePermission(String roleId, String permission);
Set<String> getEffectivePermissions(Member member);
}// Success message
EmbedTemplate.success("Title", "Description").build()
// Error message
EmbedTemplate.error("Title", "Description").build()
// Info message
EmbedTemplate.info("Title", "Description").build()
// Warning message
EmbedTemplate.warning("Title", "Description").build()ConfirmationSystem.ConfirmationMessage confirmation = ConfirmationSystem.createConfirmation(
"Are you sure?",
confirmEvent -> { /* Handle confirm */ },
cancelEvent -> { /* Handle cancel */ }
);
event.replyEmbeds(confirmation.embed())
.addActionRow(confirmation.confirmButton(), confirmation.cancelButton())
.queue();public class GreetingModule extends AbstractModule {
@Override
public void onEnable() {
registerCommand(new GreetingCommand(this));
}
@Override
public void onDisable() {
// Cleanup if needed
}
}
public class GreetingCommand extends ParentCommand {
private final GreetingModule module;
public GreetingCommand(GreetingModule module) {
this.module = module;
registerSubcommand("hello", "Say hello", null, new SubcommandHandler() {
@Override
public void execute(SlashCommandInteractionEvent event) {
if (!module.hasPermission(event.getMember(), "greet")) {
event.reply("β No permission").setEphemeral(true).queue();
return;
}
event.reply("π Hello, " + event.getUser().getAsMention() + "!").queue();
}
@Override
public SubcommandData getSubcommandData() {
return new SubcommandData("hello", "Say hello");
}
});
}
@Override
public String getName() { return "greeting"; }
@Override
public String getDescription() { return "Greeting commands"; }
}public class WelcomeModule extends AbstractModule {
@Override
public void onEnable() {
registerListener(new WelcomeListener(this));
}
@Override
public void onDisable() {
// Auto cleanup
}
}
public class WelcomeListener extends ListenerAdapter {
private final WelcomeModule module;
public WelcomeListener(WelcomeModule module) {
this.module = module;
}
@Override
public void onGuildMemberJoin(GuildMemberJoinEvent event) {
TextChannel welcomeChannel = event.getGuild().getTextChannelById("CHANNEL_ID");
if (welcomeChannel != null) {
welcomeChannel.sendMessage("Welcome " + event.getMember().getAsMention() + "!").queue();
}
}
}public class ConfigTestModule extends YamlModuleConfig {
public ConfigTestModule(Path rootPath) {
super(rootPath, "config");
}
@Override
protected void ensureDefaultValues() {
if (!this.contains("welcomeMessage")) {
this.set("welcomeMessage", "");
}
}
public String getWelcomeMessage() {
return this.getString("welcomeMessage");
}
}
public class TestModule extends AbstractModule {
@Override
protected ModuleConfig createConfig(Path dataDirectory) {
return new ConfigTestModule(dataDirectory);
}
@Override
public ConfigTestModule getConfig() {
return (ConfigTestModule) super.getConfig();
}
}Orion-API is a pure interface library. To use it, you need an implementation:
Orion-Core - The official implementation providing:
- Complete bot implementation with all API contracts
- YAML-based permission storage
- Module hot-reloading system
- Built-in management commands
- File-based configuration system
repositories {
mavenCentral()
maven { url 'https://jitpack.io' }
}
dependencies {
implementation("com.github.Arinonia:orion-api:v0.1.0-beta")
implementation("net.dv8tion:JDA:5.5.1")
implementation("org.yaml:snakeyaml:2.4")
implementation("ch.qos.logback:logback-classic:1.5.13")
}You can implement the API contracts yourself:
public class MyBot implements Bot {
private final JDA jda;
private final CommandRegistry commandRegistry;
private final PermissionManager permissionManager;
// ... other components
@Override
public CommandRegistry getCommandRegistry() {
return commandRegistry;
}
@Override
public PermissionManager getPermissionManager() {
return permissionManager;
}
// ... implement other methods
}public class Main {
public static void main(String[] args) {
OrionBot orionBot = new OrionBot("TOKEN", "GUILD_ID");
orionBot.start();
}
}
public class OrionBot implements Bot {
private static final Logger log = LoggerFactory.getLogger(OrionBot.class);
private final String token;
private final String guildId;
private JDA jda;
private ModuleManager moduleManager;
private CommandRegistry commandRegistry;
private EventRegistry eventRegistry;
private PermissionManager permissionManager;
public OrionBot(String token, String guildId) {
log.info("Initializing OrionBot... v0.0.1");
this.token = token;
this.guildId = guildId;
}
public void start() {
log.info("Starting OrionBot...");
initializeJDA();
initializeRegistries();
loadModules();
registerCommands();
logBotStatistics();
log.info("OrionBot started successfully");
}
private void initializeJDA() {
log.info("Initializing JDA...");
try {
JDABuilder builder = JDABuilder.createDefault(token)
.enableIntents(EnumSet.of(
GatewayIntent.GUILD_MESSAGES,
GatewayIntent.GUILD_MEMBERS,
GatewayIntent.MESSAGE_CONTENT,
GatewayIntent.GUILD_VOICE_STATES,
GatewayIntent.GUILD_MESSAGE_REACTIONS
));
this.jda = builder.build().awaitReady();
log.info("JDA initialized successfully");
} catch (Exception e) {
log.error("Failed to initialize JDA", e);
throw new RuntimeException("Failed to initialize JDA", e);
}
}
private void initializeRegistries() {
log.info("Initializing registries...");
this.eventRegistry = new SimpleEventRegistry(this.jda);
this.commandRegistry = new SimpleCommandRegistry(this.jda, this.guildId);
this.eventRegistry.registerListener((EventListener) this.commandRegistry);
this.eventRegistry.registerListener(new ConfirmationSystem());
this.permissionManager = new YamlPermissionManager(Path.of("permissions"));
Path modulePath = Path.of("modules");
this.moduleManager = new DefaultModuleLoader(modulePath, this);
}
private void loadModules() {
log.info("Loading modules...");
int loadedModules = this.moduleManager.loadModules();
int enabledModules = this.moduleManager.enableModules();
log.info("Loaded {} modules, enabled {} modules", loadedModules, enabledModules);
}
private void registerCommands() {
log.info("Registering commands...");
//add your built-in commands here
//this.commandRegistry.registerCommand(new PermissionCommand(this.permissionManager));
//this.commandRegistry.registerCommand(new ModulesCommand(this.moduleManager, this.permissionManager));
this.commandRegistry.synchronizeCommands();
log.info("Commands registered successfully");
}
private void logBotStatistics() {
log.info("=== Orion Bot Statistics ===");
log.info("Guild ID: {}", this.guildId != null ? this.guildId : "Not specified (using global commands)");
log.info("Modules: {} ({} enabled)",
this.moduleManager.getModules().size(),
this.moduleManager.getEnabledModules().size());
log.info("Commands: {} registered", this.commandRegistry.getCommands().size());
log.info("Permissions: {} users, {} roles",
this.permissionManager.getAllUsersWithPermissions().size(),
this.permissionManager.getAllRolesWithPermissions().size());
log.info("============================");
for (Module module : this.moduleManager.getModules()) {
String status = module.isEnabled() ? "ENABLED" : "DISABLED";
if (module.getModuleDescriptor() != null) {
log.debug("Module: {} [{}] - {}",
module.getModuleDescriptor().name(),
status,
module.getModuleDescriptor().description());
} else {
log.debug("Module: {} [{}] - No descriptor available",
module.getClass().getSimpleName(),
status);
}
}
}
private void shutdown() {
log.info("Shutting down OrionBot...");
if (this.moduleManager != null) {
this.moduleManager.disableModules();
}
if (this.jda != null) {
this.jda.shutdown();
log.info("JDA shutdown complete");
}
log.info("OrionBot shutdown completed successfully");
}
@Override
public CommandRegistry getCommandRegistry() {
return this.commandRegistry;
}
@Override
public EventRegistry getEventRegistry() {
return this.eventRegistry;
}
@Override
public JDA getJDA() {
return this.jda;
}
@Override
public ModuleManager getModuleManager() {
return this.moduleManager;
}
@Override
public PermissionManager getPermissionManager() {
return this.permissionManager;
}
}- Fork the repository
- Clone your fork:
git clone https://github.com/Arinonia/Orion-API.git
- Create a feature branch:
git checkout -b feature/new-interface
- Make changes and test
- Commit your changes:
git commit -m "Add new interface for X" - Push to your fork:
git push origin feature/new-interface
- Create a Pull Request
When contributing to the API:
- Keep interfaces focused: Single responsibility principle
- Avoid implementation details: Pure contracts only
- Use generics wisely: Type safety without complexity
- Consider extensibility: Will future implementations need flexibility?
- Backward compatibility: Don't break existing contracts
- Use Java 17 features in interfaces where appropriate
- Follow standard Java naming conventions
- Use meaningful parameter and return type names
- Keep method signatures simple and intuitive
# Compile API
./gradlew build
# Check JavaDoc generation
./gradlew javadoc
# Run any interface validation tests
./gradlew testWhen proposing API changes:
- Discuss first: Open an issue to discuss major changes
- Deprecation path: Provide clear migration for breaking changes
- Version appropriately: Follow semantic versioning
- Document changes: Update README and JavaDoc
- Event System: More granular event interfaces
- Configuration: Advanced configuration validation interfaces
- Metrics: Built-in metrics and monitoring interfaces
- Security: Enhanced permission system interfaces
- Plugin Marketplace: Standard interfaces for plugin distribution
- IDE Integration: Better development tools and templates
- Documentation: Interactive API documentation
- Testing Framework: Standard testing utilities for modules
- Community Modules: Showcase popular community modules
- Examples Repository: More comprehensive examples
- Best Practices: Guidelines for API usage
- Migration Tools: Tools for upgrading between API versions
This project is licensed under the MIT License - see the LICENSE file for details.
- JDA - Java Discord API that this builds upon
- SnakeYAML - Referenced for configuration interfaces
- SLF4J - Logging interfaces
- API Documentation: Check JavaDoc and this README
- Issues: GitHub Issues
- Discussions: GitHub Discussions
- Implementation Support: See Orion-Core for implementation-specific help TODO
- Orion-Core - Official implementation TODO
- Orion-Examples - Example modules and usage TODO
- Orion-Templates - Project templates for quick start TODO
Orion-API: Clean interfaces for Discord bot development β¨