feat: add service field support
- service: true/false is now a required field in manyplug.json - init --service creates background service template - list shows ● for services, ○ for standard plugins - validate checks service field and warns on mismatches - templates document service vs non-service behavior Services run regardless of isPluginRunning lock, but can choose to respect it. Non-services are blocked automatically.
This commit is contained in:
@@ -22,6 +22,7 @@ program
|
||||
.command('init <name>')
|
||||
.description('Create a new plugin boilerplate')
|
||||
.option('-c, --category <cat>', 'Plugin category', 'utility')
|
||||
.option('-s, --service', 'Create as a background service', false)
|
||||
.action(initCommand);
|
||||
|
||||
program
|
||||
|
||||
@@ -4,6 +4,11 @@ import chalk from 'chalk';
|
||||
|
||||
const PLUGIN_TEMPLATE = `/**
|
||||
* {{name}} plugin
|
||||
*
|
||||
* SERVICE BEHAVIOR:
|
||||
* - If service: true in manyplug.json, this plugin runs as a background service.
|
||||
* - Services can choose whether to respect api.isPluginRunning(chatId) or not.
|
||||
* - If service: false, the plugin is BLOCKED when isPluginRunning(chatId) is true.
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -11,6 +16,18 @@ const PLUGIN_TEMPLATE = `/**
|
||||
* @param {Object} ctx - { msg, chat, api }
|
||||
*/
|
||||
export default async function {{name}}Plugin({ msg, chat, api }) {
|
||||
// Check if another plugin is running in this chat
|
||||
// Only works if service: false in manyplug.json
|
||||
if (!api.isService && api.isPluginRunning && api.isPluginRunning(msg.from)) {
|
||||
// This plugin is blocked because another plugin is running
|
||||
return false;
|
||||
}
|
||||
|
||||
// For services (service: true), you can choose to respect or ignore:
|
||||
// if (!api.ignoreIsRunning && api.isPluginRunning?.(msg.from)) {
|
||||
// return false; // Service choosing to respect the lock
|
||||
// }
|
||||
|
||||
// Your plugin logic here
|
||||
// Return true to stop further processing
|
||||
return false;
|
||||
@@ -22,6 +39,7 @@ export default async function {{name}}Plugin({ msg, chat, api }) {
|
||||
*/
|
||||
export async function setup(api) {
|
||||
// Initialize databases, schedules, etc.
|
||||
// Services can use api.schedule() for background tasks
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -32,6 +50,47 @@ export const api = {
|
||||
};
|
||||
`;
|
||||
|
||||
const SERVICE_TEMPLATE = `/**
|
||||
* {{name}} service
|
||||
*
|
||||
* This is a BACKGROUND SERVICE that can run regardless of isPluginRunning state.
|
||||
* Services can optionally respect the lock by checking api.isPluginRunning(chatId).
|
||||
*/
|
||||
|
||||
/**
|
||||
* Service function - called on every message
|
||||
* Services run even when other plugins have the lock
|
||||
* @param {Object} ctx - { msg, chat, api }
|
||||
*/
|
||||
export default async function {{name}}Service({ msg, chat, api }) {
|
||||
// This service runs even when isPluginRunning is true for this chat
|
||||
// You can optionally check and respect the lock:
|
||||
|
||||
const isLocked = api.isPluginRunning?.(msg.from);
|
||||
|
||||
if (isLocked && !api.config.ignoreLock) {
|
||||
// Service choosing not to run while another plugin is active
|
||||
return false;
|
||||
}
|
||||
|
||||
// Service logic here
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup runs once when bot connects
|
||||
* Good place to start background tasks
|
||||
*/
|
||||
export async function setup(api) {
|
||||
// Services can schedule background tasks
|
||||
// api.schedule({ ... })
|
||||
}
|
||||
|
||||
export const api = {
|
||||
// Expose service methods
|
||||
};
|
||||
`;
|
||||
|
||||
export async function initCommand(name, options) {
|
||||
const pluginDir = path.resolve(name);
|
||||
|
||||
@@ -44,12 +103,14 @@ export async function initCommand(name, options) {
|
||||
|
||||
await fs.ensureDir(pluginDir);
|
||||
|
||||
const isService = options.category === 'service' || options.service;
|
||||
|
||||
// Create manyplug.json
|
||||
const manifest = {
|
||||
name,
|
||||
version: '1.0.0',
|
||||
category: options.category || 'utility',
|
||||
service: false,
|
||||
category: isService ? 'service' : (options.category || 'utility'),
|
||||
service: isService,
|
||||
description: '',
|
||||
author: '',
|
||||
dependencies: {}
|
||||
@@ -57,8 +118,9 @@ export async function initCommand(name, options) {
|
||||
|
||||
await fs.writeJson(path.join(pluginDir, 'manyplug.json'), manifest, { spaces: 2 });
|
||||
|
||||
// Create index.js
|
||||
const indexContent = PLUGIN_TEMPLATE.replace(/\{\{name\}\}/g, name);
|
||||
// Create index.js with appropriate template
|
||||
const template = isService ? SERVICE_TEMPLATE : PLUGIN_TEMPLATE;
|
||||
const indexContent = template.replace(/\{\{name\}\}/g, name);
|
||||
await fs.writeFile(path.join(pluginDir, 'index.js'), indexContent);
|
||||
|
||||
// Create locale directory
|
||||
@@ -69,7 +131,8 @@ export async function initCommand(name, options) {
|
||||
{ spaces: 2 }
|
||||
);
|
||||
|
||||
console.log(chalk.green(`✅ Plugin "${name}" created at ./${name}`));
|
||||
console.log(chalk.gray(` Category: ${options.category}`));
|
||||
console.log(chalk.green(`✅ ${isService ? 'Service' : 'Plugin'} "${name}" created at ./${name}`));
|
||||
console.log(chalk.gray(` Category: ${manifest.category}`));
|
||||
console.log(chalk.gray(` Service: ${manifest.service ? 'yes (background)' : 'no (respects isPluginRunning)'}`));
|
||||
console.log(chalk.gray(` Edit manyplug.json to add dependencies`));
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ export async function listCommand(options) {
|
||||
name: manifest.name || entry.name,
|
||||
version: manifest.version || '-',
|
||||
category: manifest.category || '-',
|
||||
service: manifest.service === true,
|
||||
status: hasEntry ? chalk.green('✓') : chalk.red('✗'),
|
||||
path: path.join('src/plugins', entry.name)
|
||||
});
|
||||
@@ -52,12 +53,14 @@ export async function listCommand(options) {
|
||||
}
|
||||
|
||||
console.log(chalk.bold('\nInstalled Plugins:\n'));
|
||||
console.log(`${chalk.gray('NAME'.padEnd(20))} ${chalk.gray('VERSION'.padEnd(10))} ${chalk.gray('CATEGORY'.padEnd(12))} STATUS`);
|
||||
console.log(chalk.gray('─'.repeat(55)));
|
||||
console.log(`${chalk.gray('NAME'.padEnd(18))} ${chalk.gray('VERSION'.padEnd(10))} ${chalk.gray('CATEGORY'.padEnd(12))} ${chalk.gray('SRV')} STATUS`);
|
||||
console.log(chalk.gray('─'.repeat(60)));
|
||||
|
||||
for (const p of plugins) {
|
||||
console.log(`${p.name.padEnd(20)} ${p.version.padEnd(10)} ${p.category.padEnd(12)} ${p.status}`);
|
||||
const serviceIcon = p.service ? chalk.cyan('●') : chalk.gray('○');
|
||||
console.log(`${p.name.padEnd(18)} ${p.version.padEnd(10)} ${p.category.padEnd(12)} ${serviceIcon} ${p.status}`);
|
||||
}
|
||||
|
||||
console.log(chalk.gray(`\nTotal: ${plugins.length} plugin(s)`));
|
||||
console.log(chalk.gray(`Legend: ${chalk.cyan('●')} service (background) ${chalk.gray('○')} standard (respects isPluginRunning)`));
|
||||
}
|
||||
|
||||
@@ -2,8 +2,9 @@ import fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
import chalk from 'chalk';
|
||||
|
||||
const REQUIRED_FIELDS = ['name', 'version', 'category'];
|
||||
const REQUIRED_FIELDS = ['name', 'version', 'category', 'service'];
|
||||
const VALID_CATEGORIES = ['games', 'media', 'utility', 'service', 'admin', 'fun'];
|
||||
const VALID_SERVICES = [true, false];
|
||||
|
||||
export async function validateCommand(pluginPath) {
|
||||
const targetPath = pluginPath ? path.resolve(pluginPath) : process.cwd();
|
||||
@@ -49,12 +50,23 @@ export async function validateCommand(pluginPath) {
|
||||
errors.push('"dependencies" must be an object');
|
||||
}
|
||||
|
||||
// Validate service field
|
||||
if (manifest.service !== undefined && !VALID_SERVICES.includes(manifest.service)) {
|
||||
errors.push('"service" must be a boolean (true or false)');
|
||||
}
|
||||
|
||||
// Warn if service is true but no category hints
|
||||
if (manifest.service === true && manifest.category !== 'service') {
|
||||
warnings.push('Plugin marked as service=true but category is not "service"');
|
||||
}
|
||||
|
||||
// Report
|
||||
if (errors.length === 0 && warnings.length === 0) {
|
||||
console.log(chalk.green('✅ Valid manyplug.json'));
|
||||
console.log(chalk.gray(` Name: ${manifest.name}`));
|
||||
console.log(chalk.gray(` Version: ${manifest.version}`));
|
||||
console.log(chalk.gray(` Category: ${manifest.category}`));
|
||||
console.log(chalk.gray(` Service: ${manifest.service === true ? 'yes (background)' : 'no (respects isPluginRunning)'}`));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user