Module System¶
StarStreamer uses a modular architecture to organize functionality into self-contained, reusable components. Modules provide the business logic and features, while plugins handle platform integrations. This separation allows for clean code organization and makes it easy to add, remove, or modify features without affecting the core framework.
What is a Module?¶
A module in StarStreamer is a self-contained package that provides features and business logic: - Groups related functionality together (chat commands, alerts, economy) - Uses plugins to interact with external platforms (Twitch, OBS, Discord) - Can be dynamically loaded and unloaded - Has its own actions, services, and configuration - Integrates seamlessly with the event system
Module vs Plugin¶
- Module = Business logic and features (what your bot does)
- Plugin = Platform integration (how your bot connects to services)
For example:
- The Chat Module provides commands like !hello and !uptime
- The Twitch Plugin handles connecting to Twitch and sending messages
- The Chat Module uses the Twitch Plugin to send responses
Module Structure¶
Each module follows a standard structure:
modules/
├── your_module/
│ ├── __init__.py # Module exports
│ ├── module.py # Module definition (extends BaseModule)
│ └── actions/ # Event handlers
│ ├── __init__.py
│ └── handlers.py # Your action handlers
Creating a Module¶
Step 1: Define Your Module Class¶
Create a module.py file that extends BaseModule:
from modules.base import BaseModule
class YourModule(BaseModule):
"""Your module description"""
@property
def module_name(self) -> str:
"""Unique identifier for this module"""
return "your_module"
@property
def description(self) -> str:
"""Human-readable description"""
return "This module does amazing things"
@property
def version(self) -> str:
"""Module version"""
return "1.0.0"
@property
def author(self) -> str:
"""Module author"""
return "Your Name"
async def initialize(self) -> None:
"""Initialize module resources"""
# Set up any resources your module needs
pass
async def shutdown(self) -> None:
"""Clean up module resources"""
# Clean up resources when module is unloaded
pass
async def register_actions(self) -> None:
"""Register action handlers"""
# Import your actions to register them
from modules.your_module.actions import handlers # noqa: F401
Step 2: Create Action Handlers¶
Add your event handlers in actions/handlers.py:
from starstreamer import on_event
from starstreamer.triggers import trigger, CommandTrigger
from starstreamer.plugins.twitch import TwitchClient # Plugin import
from starstreamer.runtime.types import Event
@on_event("twitch.chat.message")
@trigger(CommandTrigger("!yourcommand"))
async def your_command(event: Event, twitch: TwitchClient) -> None:
"""Handle your custom command using Twitch plugin"""
username = event.data['user']['display_name']
await twitch.send_message(f"Hello {username} from YourModule!")
Step 3: Register Your Module¶
The module will be automatically discovered and loaded if placed in the modules/ directory. You can also manually register it:
from modules.registry import ModuleRegistry
from modules.your_module import YourModule
registry = ModuleRegistry()
module = YourModule()
await registry.register_module(module)
Core Modules¶
StarStreamer comes with several built-in modules:
Chat Module¶
Provides basic chat commands and interactions:
- !hello - Greeting command
- !ping - Bot responsiveness check
- !commands - List available commands
- !uptime - Stream uptime
- !clip - Create a clip
Alerts Module¶
Handles stream event notifications: - Follow alerts - Subscription notifications - Raid announcements - Cheer (bits) alerts - Hype train events
RPG Module¶
Economy and gamification features:
- !balance - Check currency balance
- !work - Earn currency (5-minute cooldown)
- !daily - Daily bonus
- !give @user amount - Transfer currency
- !gamble amount - Gamble currency
- !leaderboard - Top users by balance
Module Lifecycle¶
Loading¶
- Module class is instantiated
initialize()is called to set up resourcesregister_actions()is called to register event handlers- Module is added to the registry
Runtime¶
- Event handlers respond to events
- Module can access services via dependency injection
- Module state is maintained across events
Unloading¶
shutdown()is called to clean up resources- Event handlers are unregistered
- Module is removed from the registry
Dependency Injection in Modules¶
Modules and their actions can use dependency injection to access services:
@on_event("twitch.chat.message")
@trigger(CommandTrigger("!stats"))
async def stats_command(
event: Event,
twitch: TwitchClient, # Twitch plugin injected
economy: EconomyService, # Economy service injected
logger: logging.Logger # Logger injected
) -> None:
"""Command that uses plugin and services via dependency injection"""
user_id = event.data['user']['id']
balance = await economy.get_balance(user_id)
await twitch.send_message(f"Your balance: {balance}") # Uses Twitch plugin
logger.info(f"Stats requested by user {user_id}")
Module Configuration¶
Modules can have their own configuration:
class ConfigurableModule(BaseModule):
def __init__(self, config: dict | None = None) -> None:
self.config = config or {}
self.prefix = self.config.get("command_prefix", "!")
async def initialize(self) -> None:
# Use configuration during initialization
if self.config.get("auto_greet", False):
# Set up auto-greeting
pass
Best Practices¶
1. Keep Modules Focused¶
Each module should have a single, clear purpose. Don't try to do everything in one module.
2. Use Dependency Injection¶
Don't import plugins or services directly. Use DI for better testability:
# Good - Plugin and services injected
async def handler(event: Event, twitch: TwitchClient, economy: EconomyService):
await twitch.send_message("Hello")
# Avoid - Direct imports create tight coupling
from starstreamer.plugins.twitch import twitch_client
from starstreamer.services.economy import economy_service
async def handler(event: Event):
await twitch_client.send_message("Hello")
3. Handle Errors Gracefully¶
Always handle potential errors in your modules:
@on_event("twitch.chat.message")
@trigger(CommandTrigger("!risky"))
async def risky_command(event: Event, twitch: TwitchClient, logger: logging.Logger):
try:
# Risky operation
result = await do_something_risky()
await twitch.send_message(f"Success: {result}")
except Exception as e:
logger.error(f"Error in risky command: {e}")
await twitch.send_message("Something went wrong!")
4. Document Your Module¶
Always include docstrings and type hints:
class WellDocumentedModule(BaseModule):
"""
A module that demonstrates good documentation practices.
This module provides example functionality to show
how modules should be documented.
"""
async def initialize(self) -> None:
"""
Initialize the module's resources.
Sets up database connections, loads configuration,
and prepares any required state.
"""
pass
5. Test Your Module¶
Write tests for your module's functionality:
import pytest
from modules.your_module import YourModule
@pytest.mark.asyncio
async def test_module_initialization():
module = YourModule()
await module.initialize()
assert module.module_name == "your_module"
await module.shutdown()
Advanced Topics¶
Inter-Module Communication¶
Modules can communicate through the event system:
# In module A
from starstreamer.core.event_bus import get_event_bus
async def trigger_module_b():
bus = get_event_bus()
await bus.emit("custom.module_a.event", {"data": "hello"})
# In module B
@on_event("custom.module_a.event")
async def handle_module_a_event(event: Event):
print(f"Received from module A: {event.data}")
Module Dependencies¶
Specify dependencies between modules:
class DependentModule(BaseModule):
@property
def dependencies(self) -> list[str]:
"""List of required module names"""
return ["chat", "economy"]
Hot Reloading¶
StarStreamer provides intelligent hot reloading for rapid development without losing connections or state. The system uses a hybrid approach that optimizes for both safety and development speed.
Development Mode with Auto-Reload¶
Enable hot reloading during development:
# Start with automatic file watching
uv run python src/main.py --reload
# Monitor specific directories (default: modules/, src/starstreamer/)
uv run python src/main.py --reload --watch-dirs modules,src
Hybrid Reload Strategy¶
The system intelligently chooses between hot reload and full restart based on what changed:
Hot Reload (Fast, Preserves State)
- Trigger: Changes in modules/ directory
- Behavior: Reloads only the affected module using importlib.reload()
- Preserves:
- Twitch WebSocket connections
- Database connections
- Service container state
- Event bus configuration
- Use Cases: Adding/modifying commands, fixing module logic, adjusting triggers
Full Restart (Safe, Clean State)
- Trigger: Changes in src/starstreamer/ directory
- Behavior: Graceful shutdown and complete restart
- Use Cases: Core framework changes, dependency injection modifications, event bus changes
File Watcher Details¶
The hot reload system monitors file changes with intelligent filtering:
# Monitored file patterns
*.py files in watched directories
# Ignored patterns
**/test_*.py # Test files
**/__pycache__/** # Python cache
**/*.pyc # Compiled Python
Debouncing: Changes are debounced with a 300ms delay to handle multiple rapid file modifications.
Web Interface Module Management¶
StarStreamer provides a modern web interface for module management at http://localhost:8888/modules:
Features: - Real-time module statistics - View total, enabled, and loaded module counts - Interactive module list - See all discovered modules with their current status - One-click actions - Enable, disable, and reload modules without restarting - Status indicators - Visual feedback for module states (loaded, enabled, disabled) - Hot reload support - Safely reload module code during development
Module Actions: - Load - Load an unloaded module - Enable - Activate a loaded but disabled module - Disable - Deactivate a module while keeping it loaded - Reload - Hot reload module code (useful for development)
The web interface provides a user-friendly alternative to programmatic module management and is especially useful during development and for non-technical users.
Manual Module Reloading¶
You can also reload modules programmatically:
from modules.registry import ModuleRegistry
# Reload a specific module
success = await registry.reload_module("chat")
if success:
print("Chat module reloaded successfully")
# Discover and load new modules
await registry.discover_modules()
# Get reload statistics
stats = await registry.get_reload_stats()
print(f"Successful reloads: {stats['successful']}")
print(f"Failed reloads: {stats['failed']}")
Development Workflow Example¶
-
Start development mode:
-
Modify a command in
modules/chat/actions/basic_commands.py: -
Save the file → Module hot reloads automatically → Test immediately in chat
-
Modify core framework in
src/starstreamer/core/event_bus.py→ Full restart occurs → All modules reload cleanly
Hot Reload Safety¶
The hot reload system includes several safety mechanisms:
- Exception Isolation: Failed reloads don't crash the application
- State Validation: Module state is validated after reload
- Rollback Support: Failed reloads restore the previous module version
- Handler Cleanup: Old event handlers are properly unregistered before reload
# Example of safe hot reload with error handling
try:
await registry.reload_module("problematic_module")
except ImportError as e:
logger.error(f"Module has syntax errors: {e}")
# Previous version continues running
except Exception as e:
logger.error(f"Reload failed: {e}")
# Module remains in previous working state
Performance Benefits¶
Hot reloading provides significant development speed improvements:
- No Connection Loss: Twitch EventSub connections remain active
- Preserved State: Database connections and service state maintained
- Fast Feedback: Module changes are live in ~200ms
- Reduced Downtime: No need to restart entire application for module changes
Best Practices for Hot Reloading¶
-
Structure modules for reloadability:
-
Use dependency injection instead of global imports:
-
Test hot reloads during development:
Examples¶
Simple Greeting Module¶
# modules/greeter/module.py
from modules.base import BaseModule
class GreeterModule(BaseModule):
@property
def module_name(self) -> str:
return "greeter"
async def register_actions(self) -> None:
from modules.greeter.actions import greetings # noqa: F401
# modules/greeter/actions/greetings.py
from starstreamer import on_event
from starstreamer.triggers import trigger, CommandTrigger
from starstreamer.plugins.twitch import TwitchClient # Import Twitch plugin
from starstreamer.runtime.types import Event
@on_event("twitch.follow")
async def greet_follower(event: Event, twitch: TwitchClient):
"""Handle follow events using Twitch plugin"""
username = event.data['user']['display_name']
await twitch.send_message(f"Welcome {username}! Thanks for following!")
@on_event("twitch.chat.message")
@trigger(CommandTrigger("!greet"))
async def greet_command(event: Event, twitch: TwitchClient):
"""Handle greet command using Twitch plugin"""
username = event.data['user']['display_name']
await twitch.send_message(f"Hello {username}! 👋")
Next Steps¶
- Explore the Core Modules to see examples
- Learn about Platform Plugin Development to understand how plugins work
- Understand Triggers for event filtering
- Understand Dependency Injection for accessing plugins and services
- Check out the Module API Reference