Skip to content

Arinonia/Orion-API

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

24 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Orion Discord Bot API

Java JDA License Version CI Release Issues

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.

πŸš€ Features

πŸ”§ Core API Framework

  • 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

πŸ› οΈ Developer Experience

  • Clean Contracts: Well-defined interfaces for all components
  • Flexible Implementation: Multiple implementations possible
  • Configuration: Flexible configuration system interfaces

πŸ“‹ Table of Contents

πŸ—οΈ Architecture

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

Design Principles

  • 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

πŸ“¦ Installation

Gradle

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")
}

Maven

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>

🧩 Core Concepts

Bot Interface

The main entry point providing access to all subsystems:

public interface Bot {
    CommandRegistry getCommandRegistry();
    EventRegistry getEventRegistry();
    JDA getJDA();
    ModuleManager getModuleManager();
    PermissionManager getPermissionManager();
}

Module System

Modules are the building blocks of functionality:

public interface Module {
    void onEnable();
    void onDisable();
    boolean isEnabled();
    ModuleConfig getConfig();
    boolean hasPermission(Member member, String permission);
}

Permission System

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);
}

πŸ“¦ Module Development

Creating a Module

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!");
    }
}

Module Descriptor

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: []

Commands with Permissions

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"; }
}

Event Listeners

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();
        }
    }
}

πŸ”’ Permission System

Permission Structure

The API defines a flexible permission system following the pattern: module.action

  • my_module.test - Specific action permission
  • my_module.* - All permissions for the module
  • * - Global administrator access

Permission Interfaces

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);
}

Using Permissions in Modules

// 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
}

Permission Node Utility

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"); // true

Default Implementation (YamlPermissionManager)

The 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"

πŸ“š API Reference

Core Interfaces

Bot Interface

public interface Bot {
    CommandRegistry getCommandRegistry();
    EventRegistry getEventRegistry();
    JDA getJDA();
    ModuleManager getModuleManager();
    PermissionManager getPermissionManager();
}

Module Interface

public interface Module {
    void onEnable();
    void onDisable();
    boolean isEnabled();
    ModuleConfig getConfig();
    boolean hasPermission(Member member, String permission);
}

PermissionManager Interface

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);
}

Utility Classes

EmbedTemplate

// 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

ConfirmationSystem.ConfirmationMessage confirmation = ConfirmationSystem.createConfirmation(
    "Are you sure?",
    confirmEvent -> { /* Handle confirm */ },
    cancelEvent -> { /* Handle cancel */ }
);

event.replyEmbeds(confirmation.embed())
     .addActionRow(confirmation.confirmButton(), confirmation.cancelButton())
     .queue();

πŸ’‘ Examples

Example 1: Simple Command Module

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"; }
}

Example 2: Event Listener Module

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();
        }
    }
}

Example 3: Configuration Usage

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();
    }
}

🏭 Implementations

Orion-API is a pure interface library. To use it, you need an implementation:

Official 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")
}

Creating Your Own Implementation

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
}

πŸ› οΈ Usage with Implementation

Custom Implementation

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;
    }
}

🀝 Contributing

Development Setup

  1. Fork the repository
  2. Clone your fork:
    git clone https://github.com/Arinonia/Orion-API.git
  3. Create a feature branch:
    git checkout -b feature/new-interface
  4. Make changes and test
  5. Commit your changes:
    git commit -m "Add new interface for X"
  6. Push to your fork:
    git push origin feature/new-interface
  7. Create a Pull Request

API Design Guidelines

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

Code Style

  • 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

Testing

# Compile API
./gradlew build

# Check JavaDoc generation
./gradlew javadoc

# Run any interface validation tests
./gradlew test

API Evolution

When proposing API changes:

  1. Discuss first: Open an issue to discuss major changes
  2. Deprecation path: Provide clear migration for breaking changes
  3. Version appropriately: Follow semantic versioning
  4. Document changes: Update README and JavaDoc

πŸ“ˆ Roadmap

API Enhancements

  • Event System: More granular event interfaces
  • Configuration: Advanced configuration validation interfaces
  • Metrics: Built-in metrics and monitoring interfaces
  • Security: Enhanced permission system interfaces

Ecosystem

  • 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

  • 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

πŸ“ License

This project is licensed under the MIT License - see the LICENSE file for details.

πŸ™ Acknowledgments

  • JDA - Java Discord API that this builds upon
  • SnakeYAML - Referenced for configuration interfaces
  • SLF4J - Logging interfaces

πŸ“ž Support

πŸ”— Related Projects


Orion-API: Clean interfaces for Discord bot development ✨

About

Modern Discord bot framework with plugin system

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Languages