From fc5417017142917248a509e1756c021e06777e1c Mon Sep 17 00:00:00 2001 From: synt-xerror <169557594+synt-xerror@users.noreply.github.com> Date: Thu, 9 Apr 2026 01:24:41 -0300 Subject: [PATCH] feat: add service field support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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. --- bin/manyplug.js | 1 + src/commands/init.js | 75 ++++++++++++++++++++++++++++++++++++---- src/commands/list.js | 9 +++-- src/commands/validate.js | 14 +++++++- 4 files changed, 89 insertions(+), 10 deletions(-) diff --git a/bin/manyplug.js b/bin/manyplug.js index ad3a172..368ec19 100755 --- a/bin/manyplug.js +++ b/bin/manyplug.js @@ -22,6 +22,7 @@ program .command('init ') .description('Create a new plugin boilerplate') .option('-c, --category ', 'Plugin category', 'utility') + .option('-s, --service', 'Create as a background service', false) .action(initCommand); program diff --git a/src/commands/init.js b/src/commands/init.js index b170248..994db6d 100644 --- a/src/commands/init.js +++ b/src/commands/init.js @@ -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`)); } diff --git a/src/commands/list.js b/src/commands/list.js index 663d29c..603743b 100644 --- a/src/commands/list.js +++ b/src/commands/list.js @@ -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)`)); } diff --git a/src/commands/validate.js b/src/commands/validate.js index 460c7dc..b60dc16 100644 --- a/src/commands/validate.js +++ b/src/commands/validate.js @@ -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; }