diff --git a/packages/napcat-onebot/network/plugin-manger.ts b/packages/napcat-onebot/network/plugin-manger.ts index 123016b7..a83d4ee7 100644 --- a/packages/napcat-onebot/network/plugin-manger.ts +++ b/packages/napcat-onebot/network/plugin-manger.ts @@ -54,39 +54,36 @@ export class NapCatConfig { export type PluginConfigSchema = PluginConfigItem[]; +export interface NapCatPluginContext { + core: NapCatCore; + oneBot: NapCatOneBot11Adapter; + actions: ActionMap; + pluginName: string; + pluginPath: string; + configPath: string; + dataPath: string; + NapCatConfig: typeof NapCatConfig; + adapterName: string; + pluginManager: OB11PluginMangerAdapter; +} + export interface PluginModule { - plugin_init: ( - core: NapCatCore, - obContext: NapCatOneBot11Adapter, - actions: ActionMap, - instance: OB11PluginMangerAdapter - ) => void | Promise; + plugin_init: (ctx: NapCatPluginContext) => void | Promise; plugin_onmessage?: ( - adapter: string, - core: NapCatCore, - obCtx: NapCatOneBot11Adapter, + ctx: NapCatPluginContext, event: OB11Message, - actions: ActionMap, - instance: OB11PluginMangerAdapter ) => void | Promise; plugin_onevent?: ( - adapter: string, - core: NapCatCore, - obCtx: NapCatOneBot11Adapter, + ctx: NapCatPluginContext, event: T, - actions: ActionMap, - instance: OB11PluginMangerAdapter ) => void | Promise; plugin_cleanup?: ( - core: NapCatCore, - obContext: NapCatOneBot11Adapter, - actions: ActionMap, - instance: OB11PluginMangerAdapter + ctx: NapCatPluginContext ) => void | Promise; plugin_config_schema?: PluginConfigSchema; plugin_config_ui?: PluginConfigSchema; - plugin_get_config?: () => any | Promise; - plugin_set_config?: (config: any) => void | Promise; + plugin_get_config?: (ctx: NapCatPluginContext) => any | Promise; + plugin_set_config?: (ctx: NapCatPluginContext, config: any) => void | Promise; } export interface LoadedPlugin { @@ -96,6 +93,7 @@ export interface LoadedPlugin { entryPath: string; packageJson?: PluginPackageJson; module: PluginModule; + context: NapCatPluginContext; // Store context } export interface PluginStatusConfig { @@ -106,6 +104,8 @@ export class OB11PluginMangerAdapter extends IOB11NetworkAdapter { private readonly pluginPath: string; private readonly configPath: string; private loadedPlugins: Map = new Map(); + // Track failed plugins: ID -> Error Message + private failedPlugins: Map = new Map(); declare config: PluginConfig; public NapCatConfig = NapCatConfig; @@ -251,6 +251,7 @@ export class OB11PluginMangerAdapter extends IOB11NetworkAdapter { entryPath, packageJson, module, + context: {} as NapCatPluginContext // Will be populated in registerPlugin }; await this.registerPlugin(plugin); @@ -308,6 +309,9 @@ export class OB11PluginMangerAdapter extends IOB11NetworkAdapter { return module && typeof module.plugin_init === 'function'; } + /** + * 注册插件 + */ /** * 注册插件 */ @@ -320,6 +324,26 @@ export class OB11PluginMangerAdapter extends IOB11NetworkAdapter { return; } + // Create Context + const id = plugin.name; // directory name as ID + const dataPath = path.join(this.pluginPath, id, 'data'); + const configPath = path.join(dataPath, 'config.json'); + + const context: NapCatPluginContext = { + core: this.core, + oneBot: this.obContext, + actions: this.actions, + pluginName: id, + pluginPath: plugin.pluginPath, + dataPath: dataPath, + configPath: configPath, + NapCatConfig: NapCatConfig, + adapterName: this.name, + pluginManager: this + }; + + plugin.context = context; // Store context on plugin object + this.loadedPlugins.set(plugin.name, plugin); this.logger.log( `[Plugin Adapter] Registered plugin: ${plugin.name}${plugin.version ? ` v${plugin.version}` : '' @@ -328,18 +352,16 @@ export class OB11PluginMangerAdapter extends IOB11NetworkAdapter { // 调用插件初始化方法(必须存在) try { - await plugin.module.plugin_init( - this.core, - this.obContext, - this.actions, - this - ); + await plugin.module.plugin_init(context); this.logger.log(`[Plugin Adapter] Initialized plugin: ${plugin.name}`); - } catch (error) { + } catch (error: any) { this.logger.logError( `[Plugin Adapter] Error initializing plugin ${plugin.name}:`, error ); + // Mark as failed + this.failedPlugins.set(plugin.name, error.message || 'Initialization failed'); + this.loadedPlugins.delete(plugin.name); } } @@ -355,12 +377,7 @@ export class OB11PluginMangerAdapter extends IOB11NetworkAdapter { // 调用插件清理方法 if (typeof plugin.module.plugin_cleanup === 'function') { try { - await plugin.module.plugin_cleanup( - this.core, - this.obContext, - this.actions, - this - ); + await plugin.module.plugin_cleanup(plugin.context); this.logger.log(`[Plugin Adapter] Cleaned up plugin: ${pluginName}`); } catch (error) { this.logger.logError( @@ -391,50 +408,17 @@ export class OB11PluginMangerAdapter extends IOB11NetworkAdapter { config[pluginName] = enable; this.savePluginConfig(config); - // If disabling, unload immediately if loaded if (!enable) { - // Note: pluginName passed here might be the package name or the filename/dirname - // But our registerPlugin uses plugin.name which comes from package.json or dirname. - // This mismatch is tricky. - // Ideally, we should use a consistent ID. - // Let's assume pluginName passed here effectively matches the ID used in loadedPlugins. - // But wait, loadDirectoryPlugin logic: name = packageJson.name || dirname. - // config key = dirname. - // If packageJson.name != dirname, we have a problem. - // To fix this properly: - // 1. We need to know which LoadedPlugin corresponds to the enabled/disabled item. - // 2. Or we iterate loadedPlugins and find match. - for (const [_, loaded] of this.loadedPlugins.entries()) { const dirOrFile = path.basename(loaded.pluginPath === this.pluginPath ? loaded.entryPath : loaded.pluginPath); const ext = path.extname(dirOrFile); - const simpleName = ext ? path.parse(dirOrFile).name : dirOrFile; // filename without ext - - // But wait, config key is the FILENAME (with ext for files?). - // In Scan loop: - // pluginName = path.parse(item.name).name (for file) - // pluginName = item.name (for dir) - // config[pluginName] check. - - // So if file is "test.js", pluginName is "test". Config key "test". - // If dir is "test-plugin", pluginName is "test-plugin". Config key "test-plugin". - - // loadedPlugin.name might be distinct. - // So we need to match loadedPlugin back to its fs source to unload it? - - // loadedPlugin.entryPath or pluginPath helps. - // If it's a file plugin: loaded.entryPath ends with pluginName + ext. - // If it's a dir plugin: loaded.pluginPath ends with pluginName. + const simpleName = ext ? path.parse(dirOrFile).name : dirOrFile; if (pluginName === simpleName) { this.unloadPlugin(loaded.name).catch(e => this.logger.logError('Error unloading', e)); } } } - // If enabling, we need to load it. - // But we can just rely on the API handler to call loadFile/DirectoryPlugin which now checks config. - // Wait, if I call loadFilePlugin("test.js") and config says enable=true, it loads. - // API handler needs to change to pass filename/dirname. } async onEvent (event: T) { @@ -442,7 +426,6 @@ export class OB11PluginMangerAdapter extends IOB11NetworkAdapter { return; } - // 遍历所有已加载的插件,调用它们的事件处理方法 try { await Promise.allSettled( Array.from(this.loadedPlugins.values()).map((plugin) => @@ -465,12 +448,8 @@ export class OB11PluginMangerAdapter extends IOB11NetworkAdapter { // 优先使用 plugin_onevent 方法 if (typeof plugin.module.plugin_onevent === 'function') { await plugin.module.plugin_onevent( - this.name, - this.core, - this.obContext, - event, - this.actions, - this + plugin.context, + event ); } @@ -480,12 +459,8 @@ export class OB11PluginMangerAdapter extends IOB11NetworkAdapter { typeof plugin.module.plugin_onmessage === 'function' ) { await plugin.module.plugin_onmessage( - this.name, - this.core, - this.obContext, - event as OB11Message, - this.actions, - this + plugin.context, + event as OB11Message ); } } catch (error) { diff --git a/packages/napcat-plugin-builtin/index.ts b/packages/napcat-plugin-builtin/index.ts index 5ee62a4c..0652a277 100644 --- a/packages/napcat-plugin-builtin/index.ts +++ b/packages/napcat-plugin-builtin/index.ts @@ -4,11 +4,11 @@ import type { PluginModule } from 'napcat-types/napcat-onebot/network/plugin-man import type { OB11Message, OB11PostSendMsg } from 'napcat-types/napcat-onebot/types/index'; import fs from 'fs'; -import type { PluginConfigSchema, OB11PluginMangerAdapter } from 'napcat-types/napcat-onebot/network/plugin-manger'; +import path from 'path'; +import type { PluginConfigSchema } from 'napcat-types/napcat-onebot/network/plugin-manger'; let actions: ActionMap | undefined = undefined; let startTime: number = Date.now(); -let platformInstance: OB11PluginMangerAdapter | undefined = undefined; interface BuiltinPluginConfig { prefix: string; @@ -25,62 +25,61 @@ let currentConfig: BuiltinPluginConfig = { description: '这是一个内置插件的配置示例' }; -const PLUGIN_NAME = 'napcat-plugin-builtin'; export let plugin_config_ui: PluginConfigSchema = []; /** * 插件初始化 */ -const plugin_init: PluginModule['plugin_init'] = async (_core, _obContext, _actions, _instance) => { +/** + * 插件初始化 + */ +const plugin_init: PluginModule['plugin_init'] = async (ctx) => { console.log('[Plugin: builtin] NapCat 内置插件已初始化'); - actions = _actions; - platformInstance = _instance; + actions = ctx.actions; + // platformInstance = _instance; // No longer valid or needed like this - if (_instance.NapCatConfig) { - const NapCatConfig = _instance.NapCatConfig; - plugin_config_ui = NapCatConfig.combine( - NapCatConfig.html('

👋 Welcome to NapCat Builtin Plugin

This is a demonstration of the plugin configuration interface.

'), - NapCatConfig.text('prefix', 'Command Prefix', '#napcat', 'The prefix to trigger the version info command'), - NapCatConfig.boolean('enableReply', 'Enable Reply', true, 'Switch to enable or disable the reply functionality'), - NapCatConfig.select('theme', 'Theme Selection', [ - { label: 'Light Mode', value: 'light' }, - { label: 'Dark Mode', value: 'dark' }, - { label: 'Auto', value: 'auto' } - ], 'light', 'Select a theme for the response (Demo purpose only)'), - NapCatConfig.multiSelect('features', 'Enabled Features', [ - { label: 'Version Info', value: 'version' }, - { label: 'Status Report', value: 'status' }, - { label: 'Debug Log', value: 'debug' } - ], ['version'], 'Select features to enable'), - NapCatConfig.text('description', 'Description', '这是一个内置插件的配置示例', 'A multi-line text area for notes') - ); - } + const NapCatConfig = ctx.NapCatConfig; + plugin_config_ui = NapCatConfig.combine( + NapCatConfig.html('

👋 Welcome to NapCat Builtin Plugin

This is a demonstration of the plugin configuration interface.

'), + NapCatConfig.text('prefix', 'Command Prefix', '#napcat', 'The prefix to trigger the version info command'), + NapCatConfig.boolean('enableReply', 'Enable Reply', true, 'Switch to enable or disable the reply functionality'), + NapCatConfig.select('theme', 'Theme Selection', [ + { label: 'Light Mode', value: 'light' }, + { label: 'Dark Mode', value: 'dark' }, + { label: 'Auto', value: 'auto' } + ], 'light', 'Select a theme for the response (Demo purpose only)'), + NapCatConfig.multiSelect('features', 'Enabled Features', [ + { label: 'Version Info', value: 'version' }, + { label: 'Status Report', value: 'status' }, + { label: 'Debug Log', value: 'debug' } + ], ['version'], 'Select features to enable'), + NapCatConfig.text('description', 'Description', '这是一个内置插件的配置示例', 'A multi-line text area for notes') + ); // Try to load config try { - if (platformInstance && platformInstance.getPluginConfigPath) { - const configPath = platformInstance.getPluginConfigPath(PLUGIN_NAME); - if (fs.existsSync(configPath)) { - const savedConfig = JSON.parse(fs.readFileSync(configPath, 'utf-8')); - Object.assign(currentConfig, savedConfig); - } + // Use ctx.configPath + if (fs.existsSync(ctx.configPath)) { + const savedConfig = JSON.parse(fs.readFileSync(ctx.configPath, 'utf-8')); + Object.assign(currentConfig, savedConfig); } } catch (e) { console.warn('[Plugin: builtin] Failed to load config', e); } + }; export const plugin_get_config = async () => { return currentConfig; }; -export const plugin_set_config = async (config: BuiltinPluginConfig) => { +export const plugin_set_config = async (ctx: any, config: BuiltinPluginConfig) => { currentConfig = config; - if (platformInstance && platformInstance.getPluginConfigPath) { + if (ctx && ctx.configPath) { try { - const configPath = platformInstance.getPluginConfigPath(PLUGIN_NAME); - const configDir = configPath.substring(0, configPath.lastIndexOf(process.platform === 'win32' ? '\\' : '/')); + const configPath = ctx.configPath; + const configDir = path.dirname(configPath); if (!fs.existsSync(configDir)) { fs.mkdirSync(configDir, { recursive: true }); } @@ -96,7 +95,7 @@ export const plugin_set_config = async (config: BuiltinPluginConfig) => { * 消息处理 * 当收到包含 #napcat 的消息时,回复版本信息 */ -const plugin_onmessage: PluginModule['plugin_onmessage'] = async (adapter, _core, _obCtx, event, _actions, instance) => { +const plugin_onmessage: PluginModule['plugin_onmessage'] = async (_ctx, event) => { // Use config logic const prefix = currentConfig.prefix || '#napcat'; if (currentConfig.enableReply === false) { @@ -108,11 +107,11 @@ const plugin_onmessage: PluginModule['plugin_onmessage'] = async (adapter, _core } try { - const versionInfo = await getVersionInfo(adapter, instance.config); + const versionInfo = await getVersionInfo('ob11', {}); if (!versionInfo) return; const message = formatVersionMessage(versionInfo); - await sendMessage(event, message, adapter, instance.config); + await sendMessage(event, message, 'ob11', {}); console.log('[Plugin: builtin] 已回复版本信息'); } catch (error) { diff --git a/packages/napcat-plugin-builtin/package.json b/packages/napcat-plugin-builtin/package.json index d695b0a7..37b902f9 100644 --- a/packages/napcat-plugin-builtin/package.json +++ b/packages/napcat-plugin-builtin/package.json @@ -6,7 +6,7 @@ "description": "NapCat 内置插件", "author": "NapNeko", "dependencies": { - "napcat-types": "0.0.6" + "napcat-types": "0.0.8" }, "devDependencies": { "@types/node": "^22.0.1" diff --git a/packages/napcat-plugin/README.md b/packages/napcat-plugin/README.md deleted file mode 100644 index 18552ba8..00000000 --- a/packages/napcat-plugin/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Example Plugin -## Install -安装只需要将dist产物 `index.mjs` 目录放入 napcat根目录下`plugins/example`,如果没有这个目录请创建。 \ No newline at end of file diff --git a/packages/napcat-plugin/index.ts b/packages/napcat-plugin/index.ts deleted file mode 100644 index e3c64029..00000000 --- a/packages/napcat-plugin/index.ts +++ /dev/null @@ -1,22 +0,0 @@ -import type { createActionMap } from 'napcat-types/dist/napcat-onebot/action/index.js'; -import { EventType } from 'napcat-types/dist/napcat-onebot/event/index.js'; -import type { PluginModule } from 'napcat-types/dist/napcat-onebot/network/plugin-manger'; - -/** - * 导入 napcat 包时候不使用 @/napcat...,直接使用 napcat... - * 因为 @/napcat... 会导致打包时包含整个 napcat 包,而不是只包含需要的部分 - */ - -// action 作为参数传递时请用这个 -let actionMap: ReturnType | undefined = undefined; - -const plugin_init: PluginModule['plugin_init'] = async (_core, _obContext, _actions, _instance) => { - console.log('[Plugin: example] 插件已初始化'); - actionMap = _actions; -}; -const plugin_onmessage: PluginModule['plugin_onmessage'] = async (adapter, _core, _obCtx, event, actions, instance) => { - if (event.post_type === EventType.MESSAGE && event.raw_message.includes('ping')) { - await actions.get('send_group_msg')?.handle({ group_id: String(event.group_id), message: 'pong' }, adapter, instance.config); - } -}; -export { plugin_init, plugin_onmessage, actionMap }; diff --git a/packages/napcat-plugin/package.json b/packages/napcat-plugin/package.json deleted file mode 100644 index e89cd6b0..00000000 --- a/packages/napcat-plugin/package.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "name": "napcat-plugin", - "version": "1.0.0", - "type": "module", - "main": "index.mjs", - "description": "一个高级的 NapCat 插件示例", - "dependencies": { - "napcat-types": "workspace:*" - }, - "devDependencies": { - "@types/node": "^22.0.1" - }, - "scripts": { - "build": "vite build" - } -} \ No newline at end of file diff --git a/packages/napcat-plugin/tsconfig.json b/packages/napcat-plugin/tsconfig.json deleted file mode 100644 index 48d3a03b..00000000 --- a/packages/napcat-plugin/tsconfig.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "extends": "../../tsconfig.base.json", - "include": [ - "*.ts", - "**/*.ts" - ], - "exclude": [ - "node_modules", - "dist" - ] -} \ No newline at end of file diff --git a/packages/napcat-plugin/vite.config.ts b/packages/napcat-plugin/vite.config.ts deleted file mode 100644 index 5a8de9cb..00000000 --- a/packages/napcat-plugin/vite.config.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { defineConfig } from 'vite'; -import { resolve } from 'path'; -import nodeResolve from '@rollup/plugin-node-resolve'; -import { builtinModules } from 'module'; - -const nodeModules = [...builtinModules, builtinModules.map((m) => `node:${m}`)].flat(); - -export default defineConfig({ - resolve: { - conditions: ['node', 'default'], - alias: { - '@/napcat-core': resolve(__dirname, '../napcat-core'), - '@': resolve(__dirname, '../'), - }, - }, - build: { - sourcemap: false, - target: 'esnext', - minify: false, - lib: { - entry: 'index.ts', - formats: ['es'], - fileName: () => 'index.mjs', - }, - rollupOptions: { - external: [...nodeModules], - }, - }, - plugins: [nodeResolve()], -}); diff --git a/packages/napcat-types/package.public.json b/packages/napcat-types/package.public.json index 93899a5c..ad583299 100644 --- a/packages/napcat-types/package.public.json +++ b/packages/napcat-types/package.public.json @@ -1,6 +1,6 @@ { "name": "napcat-types", - "version": "0.0.6", + "version": "0.0.8", "private": false, "type": "module", "types": "./napcat-types/index.d.ts", diff --git a/packages/napcat-webui-backend/src/api/Plugin.ts b/packages/napcat-webui-backend/src/api/Plugin.ts index 968bc54a..221dd703 100644 --- a/packages/napcat-webui-backend/src/api/Plugin.ts +++ b/packages/napcat-webui-backend/src/api/Plugin.ts @@ -226,12 +226,13 @@ export const GetPluginConfigHandler: RequestHandler = async (req, res) => { if (plugin.module.plugin_get_config) { try { - config = await plugin.module.plugin_get_config(); + config = await plugin.module.plugin_get_config(plugin.context); } catch (e) { } } else if (schema) { // Default behavior: read from default config path try { - const configPath = pluginManager.getPluginConfigPath(name); + // Use context configPath if available + const configPath = plugin.context?.configPath || pluginManager.getPluginConfigPath(name); if (fs.existsSync(configPath)) { config = JSON.parse(fs.readFileSync(configPath, 'utf-8')); } @@ -253,7 +254,7 @@ export const SetPluginConfigHandler: RequestHandler = async (req, res) => { if (plugin.module.plugin_set_config) { try { - await plugin.module.plugin_set_config(config); + await plugin.module.plugin_set_config(plugin.context, config); return sendSuccess(res, { message: 'Config updated' }); } catch (e: any) { return sendError(res, 'Error updating config: ' + e.message); @@ -261,7 +262,8 @@ export const SetPluginConfigHandler: RequestHandler = async (req, res) => { } else if (plugin.module.plugin_config_schema || plugin.module.plugin_config_ui) { // Default behavior: write to default config path try { - const configPath = pluginManager.getPluginConfigPath(name); + const configPath = plugin.context?.configPath || pluginManager.getPluginConfigPath(name); + const configDir = path.dirname(configPath); if (!fs.existsSync(configDir)) { fs.mkdirSync(configDir, { recursive: true }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c5b29048..78bd2bdb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -223,8 +223,8 @@ importers: packages/napcat-plugin-builtin: dependencies: napcat-types: - specifier: 0.0.6 - version: 0.0.6 + specifier: 0.0.7 + version: 0.0.7 devDependencies: '@types/node': specifier: ^22.0.1 @@ -5448,8 +5448,8 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - napcat-types@0.0.6: - resolution: {integrity: sha512-KyIEr/uFC8w1bGF2Oyvk+2Kdr6ENklWK9bHwrGGbAKnUUJ4GRhsUYQdX/dDhhiZrLFWisYslQyLFD6530YtTlg==} + napcat-types@0.0.7: + resolution: {integrity: sha512-PIDaQ6YnTxxpC1yb+VIDsQDamV6Ry+fUhHUBCXUVSzj1outisf6jSKFr2O7c9wX9zdX6Pi6NaVECNRy0ob4dmg==} napcat.protobuf@1.1.4: resolution: {integrity: sha512-z7XtLSBJ/PxmYb0VD/w+eYr/X3LyGz+SZ2QejFTOczwt6zWNxy2yV1mTMTvJoc3BWkI3ESVFRxkuT6+pj1tb1Q==} @@ -12819,7 +12819,7 @@ snapshots: nanoid@3.3.11: {} - napcat-types@0.0.6: + napcat-types@0.0.7: dependencies: '@sinclair/typebox': 0.34.41 '@types/cors': 2.8.19