Plugin Interfaces and Base Classes
Overview
The QuantumKat Plugin SDK provides several core interfaces and base classes that define the contract and behavior for plugins. This document covers all the essential interfaces that plugins can implement.
Core Interfaces
IPlugin
The foundational interface that all plugins must implement.
public interface IPlugin
{
string Name { get; }
string Description { get; }
string Version { get; }
string Author { get; }
Dictionary<string, List<string>> PluginDependencies { get; }
void Initialize(PluginBootstrapContext context);
void RegisterServices(IServiceCollection services);
}
Properties
- Name: A unique identifier for the plugin
- Description: A brief description of what the plugin does
- Version: The plugin version (used for dependency resolution)
- Author: The plugin author's name
- PluginDependencies: Dependencies on other plugins with version requirements
Methods
- Initialize: Called during plugin loading to set up the plugin
- RegisterServices: Called to register the plugin's services with the DI container
Example Implementation
public class MyPlugin : IPlugin
{
public string Name => "MyAwesomePlugin";
public string Description => "Does awesome things";
public string Version => "1.0.0";
public string Author => "John Doe";
public Dictionary<string, List<string>> PluginDependencies => new()
{
["SomeRequiredPlugin"] = [">=1.2.0", "<2.0.0"]
};
public void Initialize(PluginBootstrapContext context)
{
var logger = context.Logger;
logger.LogInformation("MyAwesomePlugin initialized");
// Access configuration
var config = context.Configuration;
var myValue = config["MyPlugin:SomeValue"];
// Access other services
var someService = context.CoreServices.GetService<ISomeService>();
}
public void RegisterServices(IServiceCollection services)
{
services.AddSingleton<IMyPluginService, MyPluginService>();
services.AddTransient<IMyHelper, MyHelper>();
}
}
IThreadedPlugin
An extension of IPlugin for plugins that need to run background tasks or services.
public interface IThreadedPlugin : IPlugin
{
Task StartAsync(CancellationToken cancellationToken);
Task StopAsync(CancellationToken cancellationToken);
}
When to Use
- Plugins that need to run continuous background processes
- Plugins that host services (like web servers, schedulers, etc.)
- Plugins that need graceful startup/shutdown handling
Example Implementation
public class BackgroundServicePlugin : IThreadedPlugin
{
private readonly Timer _timer;
private bool _isRunning;
public string Name => "BackgroundService";
public string Description => "Runs background tasks";
public string Version => "1.0.0";
public string Author => "Plugin Author";
public Dictionary<string, List<string>> PluginDependencies => new();
public void Initialize(PluginBootstrapContext context)
{
// Initialization logic
}
public void RegisterServices(IServiceCollection services)
{
services.AddSingleton<IBackgroundTaskService, BackgroundTaskService>();
}
public async Task StartAsync(CancellationToken cancellationToken)
{
_isRunning = true;
// Start your background service
_timer = new Timer(DoWork, null, TimeSpan.Zero, TimeSpan.FromMinutes(1));
await Task.CompletedTask;
}
public async Task StopAsync(CancellationToken cancellationToken)
{
_isRunning = false;
// Cleanup resources
_timer?.Dispose();
await Task.CompletedTask;
}
private void DoWork(object? state)
{
if (!_isRunning) return;
// Your background work here
}
}
IPluginEventRegistry
Interface for registering event handlers within plugins.
public interface IPluginEventRegistry
{
void SubscribeToMessage(string name, Func<IMessage, Task<bool>> predicate, Func<IMessage, Task> handler);
void UnsubscribeFromMessage(string name);
void ClearAllSubscriptions();
bool IsSubscribed(string name);
Dictionary<string, (Func<IMessage, Task<bool>> predicate, Func<IMessage, Task> handler)> GetSubscriptions();
}
Methods
- SubscribeToMessage: Register a named event handler with an async predicate filter
- UnsubscribeFromMessage: Remove a specific subscription by name
- ClearAllSubscriptions: Remove all subscriptions from the calling plugin
- IsSubscribed: Check if a subscription exists
- GetSubscriptions: Get all subscriptions for the calling plugin
Usage in Plugins
public void Initialize(PluginBootstrapContext context)
{
var eventRegistry = context.EventRegistry;
// Subscribe to specific message content
eventRegistry?.SubscribeToMessage(
"hello-handler",
async message => {
// Async predicate - returns true if message should be handled
return message.Content.StartsWith("!hello");
},
async message => {
// Handle the message
await message.Channel.SendMessageAsync("Hello there!");
});
// Subscribe to messages from specific channels
eventRegistry?.SubscribeToMessage(
"bot-commands-handler",
async message => {
return message.Channel.Name == "bot-commands";
},
HandleBotCommand);
}
private async Task HandleBotCommand(IMessage message)
{
// Handle the command
}
Subscription Management
public void Initialize(PluginBootstrapContext context)
{
var eventRegistry = context.EventRegistry;
// Register handlers
eventRegistry?.SubscribeToMessage(
"my-handler",
async msg => msg.Content.Contains("test"),
HandleTestMessage);
// Check if subscribed
bool isSubscribed = eventRegistry?.IsSubscribed("my-handler") ?? false;
// Get all subscriptions for debugging
var subs = eventRegistry?.GetSubscriptions();
// Unsubscribe from a specific handler
eventRegistry?.UnsubscribeFromMessage("my-handler");
// Clear all subscriptions from this plugin
eventRegistry?.ClearAllSubscriptions();
}
IPluginServiceProvider
Interface for managing shared services between the main application and plugins.
public interface IPluginServiceProvider
{
IServiceProvider ServiceProvider { get; }
void RebuildServiceProvider();
void RegisterServicesAndRebuild(Action<IServiceCollection> serviceRegistration);
}
Dynamic Service Registration
public void Initialize(PluginBootstrapContext context)
{
// Register additional services dynamically
context.SharedServiceProvider?.RegisterServicesAndRebuild(services =>
{
services.AddScoped<IDynamicService, DynamicService>();
});
}
Plugin Dependency Management
Version Requirements
The SDK supports semantic version constraints:
==1.2.3- Exactly version 1.2.3>=1.2.0- Version 1.2.0 or higher<=2.0.0- Version 2.0.0 or lower>1.1.0- Higher than version 1.1.0<3.0.0- Lower than version 3.0.01.2.3- Exactly version 1.2.3 (same as ==)
Multiple Constraints
public Dictionary<string, List<string>> PluginDependencies => new()
{
["DatabasePlugin"] = [">=1.0.0", "<2.0.0"],
["UtilityPlugin"] = ["==1.5.0"],
["LoggingPlugin"] = [">1.2.0"]
};
Best Practices
1. Plugin Naming
- Use descriptive, unique names
- Avoid spaces and special characters
- Consider namespacing for organization
2. Version Management
- Follow semantic versioning (SemVer)
- Update versions when making breaking changes
- Document version compatibility
3. Service Registration
- Register interfaces, not concrete classes when possible
- Use appropriate service lifetimes (Singleton, Scoped, Transient)
- Avoid registering services with conflicting names
4. Error Handling
- Always handle exceptions in event handlers
- Log errors appropriately
- Provide meaningful error messages
5. Resource Management
- Implement IDisposable when managing resources
- Clean up properly in StopAsync for threaded plugins
- Avoid memory leaks in long-running plugins
Common Patterns
Configuration-Driven Plugin
public class ConfigurablePlugin : IPlugin
{
private PluginConfig _config;
public void Initialize(PluginBootstrapContext context)
{
_config = context.Configuration
.GetSection("MyPlugin")
.Get<PluginConfig>() ?? new PluginConfig();
}
}
public class PluginConfig
{
public bool EnableFeature { get; set; } = true;
public int MaxRetries { get; set; } = 3;
public string ConnectionString { get; set; } = "";
}
Service-Dependent Plugin
public class ServiceDependentPlugin : IPlugin
{
private ILogger _logger;
private IMyRequiredService _requiredService;
public void Initialize(PluginBootstrapContext context)
{
_logger = context.Logger;
_requiredService = context.CoreServices.GetRequiredService<IMyRequiredService>();
}
}