327 líneas
7.9 KiB
Markdown
327 líneas
7.9 KiB
Markdown
# Module Development Guide
|
|
|
|
## Introduction
|
|
|
|
Modules are the primary way to extend the Prosody Node.js server. They can add new features, handle stanzas, store data, and integrate with external services.
|
|
|
|
## Module Structure
|
|
|
|
A basic module consists of a JavaScript file that exports an object with specific properties:
|
|
|
|
```javascript
|
|
module.exports = {
|
|
name: 'example',
|
|
version: '1.0.0',
|
|
description: 'An example module',
|
|
author: 'Your Name',
|
|
dependencies: [], // Other modules this depends on
|
|
|
|
load(module) {
|
|
// Called when module is loaded
|
|
},
|
|
|
|
unload(module) {
|
|
// Called when module is unloaded
|
|
}
|
|
};
|
|
```
|
|
|
|
## Module API
|
|
|
|
The `module` object passed to `load()` provides access to the server API:
|
|
|
|
### Properties
|
|
|
|
- `module.name` - Module name
|
|
- `module.host` - Virtual host this module is loaded for
|
|
- `module.api` - API object with helper functions
|
|
|
|
### API Object
|
|
|
|
```javascript
|
|
module.api = {
|
|
config, // Configuration manager
|
|
hostManager, // Host manager
|
|
sessionManager, // Session manager
|
|
stanzaRouter, // Stanza router
|
|
storageManager, // Storage manager
|
|
events, // Event emitter
|
|
logger, // Logger instance
|
|
|
|
// Helper methods
|
|
getConfig(key, defaultValue),
|
|
getGlobalConfig(key, defaultValue),
|
|
getHostConfig(key, defaultValue),
|
|
require(moduleName) // Load another module
|
|
};
|
|
```
|
|
|
|
## Examples
|
|
|
|
### 1. Simple Echo Module
|
|
|
|
```javascript
|
|
// modules/mod_echo.js
|
|
module.exports = {
|
|
name: 'echo',
|
|
version: '1.0.0',
|
|
description: 'Echoes messages back to sender',
|
|
|
|
load(module) {
|
|
const { logger, stanzaRouter } = module.api;
|
|
|
|
logger.info('Echo module loaded');
|
|
|
|
module.hook('message', (stanza, session) => {
|
|
if (stanza.attrs.type === 'chat') {
|
|
const body = stanza.getChildText('body');
|
|
if (body) {
|
|
// Create echo response
|
|
const response = stanza.clone();
|
|
response.attrs.from = stanza.attrs.to;
|
|
response.attrs.to = stanza.attrs.from;
|
|
|
|
session.send(response);
|
|
logger.debug(`Echoed message to ${stanza.attrs.from}`);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
};
|
|
```
|
|
|
|
### 2. Message Logger Module
|
|
|
|
```javascript
|
|
// modules/mod_message_logger.js
|
|
module.exports = {
|
|
name: 'message_logger',
|
|
version: '1.0.0',
|
|
description: 'Logs all messages to storage',
|
|
|
|
async load(module) {
|
|
const { logger, stanzaRouter, storageManager } = module.api;
|
|
const store = storageManager.getStore(module.host, 'message_log');
|
|
|
|
logger.info('Message logger module loaded');
|
|
|
|
stanzaRouter.on('message', async (stanza, session) => {
|
|
try {
|
|
const logEntry = {
|
|
timestamp: Date.now(),
|
|
from: stanza.attrs.from,
|
|
to: stanza.attrs.to,
|
|
type: stanza.attrs.type,
|
|
body: stanza.getChildText('body')
|
|
};
|
|
|
|
const key = `msg_${Date.now()}_${Math.random()}`;
|
|
await store.set(key, logEntry);
|
|
|
|
logger.debug('Message logged');
|
|
} catch (error) {
|
|
logger.error('Failed to log message:', error);
|
|
}
|
|
});
|
|
}
|
|
};
|
|
```
|
|
|
|
### 3. User Statistics Module
|
|
|
|
```javascript
|
|
// modules/mod_user_stats.js
|
|
module.exports = {
|
|
name: 'user_stats',
|
|
version: '1.0.0',
|
|
description: 'Tracks user statistics',
|
|
|
|
load(module) {
|
|
const { logger, sessionManager, storageManager } = module.api;
|
|
const store = storageManager.getStore(module.host, 'user_stats');
|
|
|
|
const stats = {
|
|
totalMessages: 0,
|
|
totalPresence: 0,
|
|
onlineUsers: new Set()
|
|
};
|
|
|
|
// Track sessions
|
|
sessionManager.on('session:authenticated', (session) => {
|
|
stats.onlineUsers.add(session.bareJid);
|
|
logger.info(`User online: ${session.bareJid} (total: ${stats.onlineUsers.size})`);
|
|
});
|
|
|
|
sessionManager.on('session:closed', (session) => {
|
|
stats.onlineUsers.delete(session.bareJid);
|
|
logger.info(`User offline: ${session.bareJid} (total: ${stats.onlineUsers.size})`);
|
|
});
|
|
|
|
// Track stanzas
|
|
module.on('message', () => {
|
|
stats.totalMessages++;
|
|
});
|
|
|
|
module.on('presence', () => {
|
|
stats.totalPresence++;
|
|
});
|
|
|
|
// Expose stats via IQ
|
|
module.hook('iq', (stanza, session) => {
|
|
if (stanza.attrs.type === 'get') {
|
|
const query = stanza.getChild('query');
|
|
if (query && query.attrs.xmlns === 'http://example.com/stats') {
|
|
const response = new ltx.Element('iq', {
|
|
type: 'result',
|
|
id: stanza.attrs.id,
|
|
to: stanza.attrs.from
|
|
}).c('query', { xmlns: 'http://example.com/stats' })
|
|
.c('stat', { name: 'messages' }).t(stats.totalMessages.toString()).up()
|
|
.c('stat', { name: 'presence' }).t(stats.totalPresence.toString()).up()
|
|
.c('stat', { name: 'online' }).t(stats.onlineUsers.size.toString());
|
|
|
|
session.send(response);
|
|
return true; // Handled
|
|
}
|
|
}
|
|
});
|
|
}
|
|
};
|
|
```
|
|
|
|
### 4. Rate Limiting Module
|
|
|
|
```javascript
|
|
// modules/mod_rate_limit.js
|
|
const { RateLimiterMemory } = require('rate-limiter-flexible');
|
|
|
|
module.exports = {
|
|
name: 'rate_limit',
|
|
version: '1.0.0',
|
|
description: 'Rate limits stanzas per user',
|
|
|
|
load(module) {
|
|
const { logger, stanzaRouter, config } = module.api;
|
|
|
|
const limiter = new RateLimiterMemory({
|
|
points: module.api.getConfig('points', 10),
|
|
duration: module.api.getConfig('duration', 1)
|
|
});
|
|
|
|
// Add filter to stanza router
|
|
stanzaRouter.addFilter(async (stanza, session) => {
|
|
if (!session || !session.bareJid) return true;
|
|
|
|
try {
|
|
await limiter.consume(session.bareJid);
|
|
return true; // Allow stanza
|
|
} catch (error) {
|
|
logger.warn(`Rate limit exceeded for ${session.bareJid}`);
|
|
return false; // Block stanza
|
|
}
|
|
});
|
|
|
|
logger.info('Rate limiting enabled');
|
|
}
|
|
};
|
|
```
|
|
|
|
## Hooks and Events
|
|
|
|
### Stanza Events
|
|
|
|
- `message` - Message stanza received
|
|
- `presence` - Presence stanza received
|
|
- `iq` - IQ stanza received
|
|
- `iq:get` - IQ get request
|
|
- `iq:set` - IQ set request
|
|
- `iq:result` - IQ result
|
|
- `iq:error` - IQ error
|
|
|
|
### Session Events
|
|
|
|
- `session:created` - New session created
|
|
- `session:authenticated` - Session authenticated
|
|
- `session:closed` - Session closed
|
|
|
|
### Server Events
|
|
|
|
- `server:started` - Server started
|
|
- `server:stopped` - Server stopped
|
|
|
|
## Storage
|
|
|
|
Modules can store persistent data:
|
|
|
|
```javascript
|
|
const store = storageManager.getStore(module.host, 'mydata');
|
|
|
|
// Store data
|
|
await store.set('key', { data: 'value' });
|
|
|
|
// Retrieve data
|
|
const data = await store.get('key');
|
|
|
|
// Delete data
|
|
await store.delete('key');
|
|
|
|
// Find data
|
|
const results = await store.find(item => item.type === 'important');
|
|
```
|
|
|
|
## Configuration
|
|
|
|
Modules can access configuration:
|
|
|
|
```javascript
|
|
// Module-specific config from moduleConfig section
|
|
const enabled = module.api.getConfig('enabled', true);
|
|
|
|
// Global config
|
|
const domain = module.api.getGlobalConfig('server.domain');
|
|
|
|
// Host-specific config
|
|
const modules = module.api.getHostConfig('modules', []);
|
|
```
|
|
|
|
## Best Practices
|
|
|
|
1. **Error Handling**: Always use try-catch blocks
|
|
2. **Logging**: Log important events and errors
|
|
3. **Cleanup**: Implement proper cleanup in `unload()`
|
|
4. **Performance**: Avoid blocking operations
|
|
5. **Testing**: Write tests for your modules
|
|
6. **Documentation**: Document module configuration
|
|
|
|
## Testing
|
|
|
|
```javascript
|
|
// test/mod_example.test.js
|
|
const ModuleManager = require('../src/core/module-manager');
|
|
|
|
describe('Example Module', () => {
|
|
it('should load successfully', async () => {
|
|
// Test module loading
|
|
});
|
|
|
|
it('should handle messages', async () => {
|
|
// Test message handling
|
|
});
|
|
});
|
|
```
|
|
|
|
## Publishing
|
|
|
|
To share your module:
|
|
|
|
1. Create a repository
|
|
2. Add proper documentation
|
|
3. Include example configuration
|
|
4. Publish to npm (optional)
|
|
|
|
## Resources
|
|
|
|
- [XMPP RFCs](https://xmpp.org/rfcs/)
|
|
- [XMPP Extensions (XEPs)](https://xmpp.org/extensions/)
|
|
- [Module Examples](examples/modules/)
|