Refactor plugin manager with modular loader and types

Refactors the plugin manager by extracting configuration, loader, and type definitions into separate modules under the 'plugin' directory. Introduces a new PluginLoader class for scanning and loading plugins, and updates the main manager to use modularized logic and improved type safety. This change improves maintainability, separation of concerns, and extensibility for plugin management.
This commit is contained in:
手瓜一十雪
2026-01-30 11:50:22 +08:00
parent 8b8f9987a1
commit 6a9437cb3b
9 changed files with 1615 additions and 664 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,39 @@
import { PluginConfigItem, PluginConfigSchema } from './types';
/**
* NapCat 插件配置构建器
* 提供便捷的配置项创建方法
*/
export class NapCatConfig {
static text (key: string, label: string, defaultValue?: string, description?: string, reactive?: boolean): PluginConfigItem {
return { key, type: 'string', label, default: defaultValue, description, reactive };
}
static number (key: string, label: string, defaultValue?: number, description?: string, reactive?: boolean): PluginConfigItem {
return { key, type: 'number', label, default: defaultValue, description, reactive };
}
static boolean (key: string, label: string, defaultValue?: boolean, description?: string, reactive?: boolean): PluginConfigItem {
return { key, type: 'boolean', label, default: defaultValue, description, reactive };
}
static select (key: string, label: string, options: { label: string; value: string | number; }[], defaultValue?: string | number, description?: string, reactive?: boolean): PluginConfigItem {
return { key, type: 'select', label, options, default: defaultValue, description, reactive };
}
static multiSelect (key: string, label: string, options: { label: string; value: string | number; }[], defaultValue?: (string | number)[], description?: string, reactive?: boolean): PluginConfigItem {
return { key, type: 'multi-select', label, options, default: defaultValue, description, reactive };
}
static html (content: string): PluginConfigItem {
return { key: `_html_${Math.random().toString(36).slice(2)}`, type: 'html', label: '', default: content };
}
static plainText (content: string): PluginConfigItem {
return { key: `_text_${Math.random().toString(36).slice(2)}`, type: 'text', label: '', default: content };
}
static combine (...items: PluginConfigItem[]): PluginConfigSchema {
return items;
}
}

View File

@@ -0,0 +1,23 @@
// 导出类型
export type {
PluginPackageJson,
PluginConfigItem,
PluginConfigSchema,
INapCatConfigStatic,
NapCatConfigClass,
IPluginManager,
PluginConfigUIController,
PluginLogger,
NapCatPluginContext,
PluginModule,
PluginRuntimeStatus,
PluginRuntime,
PluginEntry,
PluginStatusConfig,
} from './types';
// 导出配置构建器
export { NapCatConfig } from './config';
// 导出加载器
export { PluginLoader } from './loader';

View File

@@ -0,0 +1,298 @@
import fs from 'fs';
import path from 'path';
import { LogWrapper } from 'napcat-core/helper/log';
import {
PluginPackageJson,
PluginModule,
PluginEntry,
PluginStatusConfig,
} from './types';
/**
* 插件加载器
* 负责扫描、加载和导入插件模块
*/
export class PluginLoader {
constructor (
private readonly pluginPath: string,
private readonly configPath: string,
private readonly logger: LogWrapper
) { }
/**
* 加载插件状态配置
*/
loadPluginStatusConfig (): PluginStatusConfig {
if (fs.existsSync(this.configPath)) {
try {
return JSON.parse(fs.readFileSync(this.configPath, 'utf-8'));
} catch (e) {
this.logger.logWarn('[PluginLoader] Error parsing plugins.json', e);
}
}
return {};
}
/**
* 保存插件状态配置
*/
savePluginStatusConfig (config: PluginStatusConfig): void {
try {
fs.writeFileSync(this.configPath, JSON.stringify(config, null, 2), 'utf-8');
} catch (e) {
this.logger.logError('[PluginLoader] Error saving plugins.json', e);
}
}
/**
* 扫描插件目录,收集所有有效插件条目(异步版本,验证模块有效性)
* 只有包含有效 plugin_init 函数的插件才会被收集
*/
async scanPlugins (): Promise<PluginEntry[]> {
const entries: PluginEntry[] = [];
// 确保插件目录存在
if (!fs.existsSync(this.pluginPath)) {
this.logger.logWarn(`[PluginLoader] Plugin directory does not exist: ${this.pluginPath}`);
fs.mkdirSync(this.pluginPath, { recursive: true });
return entries;
}
const items = fs.readdirSync(this.pluginPath, { withFileTypes: true });
const statusConfig = this.loadPluginStatusConfig();
for (const item of items) {
if (!item.isDirectory()) {
continue;
}
const entry = this.scanDirectoryPlugin(item.name, statusConfig);
if (!entry) {
continue;
}
// 如果没有入口文件,跳过
if (!entry.entryPath) {
this.logger.logWarn(`[PluginLoader] Skipping ${item.name}: no entry file found`);
continue;
}
// 如果插件被禁用,跳过模块验证,直接添加到列表
if (!entry.enable) {
entries.push(entry);
continue;
}
// 验证模块有效性(仅对启用的插件)
const validation = await this.validatePluginEntry(entry.entryPath);
if (!validation.valid) {
this.logger.logWarn(`[PluginLoader] Skipping ${item.name}: ${validation.error}`);
continue;
}
entries.push(entry);
}
return entries;
}
/**
* 扫描单个目录插件
*/
private scanDirectoryPlugin (dirname: string, statusConfig: PluginStatusConfig): PluginEntry | null {
const pluginDir = path.join(this.pluginPath, dirname);
try {
// 尝试读取 package.json
let packageJson: PluginPackageJson | undefined;
const packageJsonPath = path.join(pluginDir, 'package.json');
if (fs.existsSync(packageJsonPath)) {
try {
const packageContent = fs.readFileSync(packageJsonPath, 'utf-8');
packageJson = JSON.parse(packageContent);
} catch (error) {
this.logger.logWarn(`[PluginLoader] Invalid package.json in ${dirname}:`, error);
}
}
// 获取插件 ID包名或目录名
const pluginId = packageJson?.name || dirname;
// 确定入口文件
const entryFile = this.findEntryFile(pluginDir, packageJson);
const entryPath = entryFile ? path.join(pluginDir, entryFile) : undefined;
// 获取启用状态(默认启用)
const enable = statusConfig[pluginId] !== false;
// 创建插件条目
const entry: PluginEntry = {
id: pluginId,
fileId: dirname,
name: packageJson?.name,
version: packageJson?.version,
description: packageJson?.description,
author: packageJson?.author,
pluginPath: pluginDir,
entryPath,
packageJson,
enable,
loaded: false,
runtime: {
status: 'unloaded',
},
};
// 如果没有入口文件,标记为错误
if (!entryPath) {
entry.runtime = {
status: 'error',
error: `No valid entry file found for plugin directory: ${dirname}`,
};
}
return entry;
} catch (error: any) {
// 创建错误条目
return {
id: dirname, // 使用目录名作为 ID
fileId: dirname,
pluginPath: path.join(this.pluginPath, dirname),
enable: statusConfig[dirname] !== false,
loaded: false,
runtime: {
status: 'error',
error: error.message || 'Unknown error during scan',
},
};
}
}
/**
* 查找插件目录的入口文件
*/
private findEntryFile (pluginDir: string, packageJson?: PluginPackageJson): string | null {
const possibleEntries = [
packageJson?.main,
'index.mjs',
'index.js',
'main.mjs',
'main.js',
].filter(Boolean) as string[];
for (const entry of possibleEntries) {
const entryPath = path.join(pluginDir, entry);
if (fs.existsSync(entryPath) && fs.statSync(entryPath).isFile()) {
return entry;
}
}
return null;
}
/**
* 动态导入模块
*/
async importModule (filePath: string): Promise<any> {
const fileUrl = `file://${filePath.replace(/\\/g, '/')}`;
const fileUrlWithQuery = `${fileUrl}?t=${Date.now()}`;
return await import(fileUrlWithQuery);
}
/**
* 加载插件模块
*/
async loadPluginModule (entry: PluginEntry): Promise<PluginModule | null> {
if (!entry.entryPath) {
entry.runtime = {
status: 'error',
error: 'No entry path specified',
};
return null;
}
try {
const module = await this.importModule(entry.entryPath);
if (!this.isValidPluginModule(module)) {
entry.runtime = {
status: 'error',
error: 'Invalid plugin module: missing plugin_init function',
};
return null;
}
return module;
} catch (error: any) {
entry.runtime = {
status: 'error',
error: error.message || 'Failed to import module',
};
return null;
}
}
/**
* 检查模块是否为有效的插件模块
*/
isValidPluginModule (module: any): module is PluginModule {
return module && typeof module.plugin_init === 'function';
}
/**
* 验证插件入口文件是否包含有效的 plugin_init 函数
* 用于扫描阶段快速验证
*/
async validatePluginEntry (entryPath: string): Promise<{ valid: boolean; error?: string; }> {
try {
const module = await this.importModule(entryPath);
if (this.isValidPluginModule(module)) {
return { valid: true };
}
return { valid: false, error: 'Missing plugin_init function' };
} catch (error: any) {
return { valid: false, error: error.message || 'Failed to import module' };
}
}
/**
* 重新扫描单个插件
*/
rescanPlugin (dirname: string): PluginEntry | null {
const statusConfig = this.loadPluginStatusConfig();
return this.scanDirectoryPlugin(dirname, statusConfig);
}
/**
* 通过 ID 查找插件目录名
*/
findPluginDirById (pluginId: string): string | null {
if (!fs.existsSync(this.pluginPath)) {
return null;
}
const items = fs.readdirSync(this.pluginPath, { withFileTypes: true });
for (const item of items) {
if (!item.isDirectory()) continue;
const packageJsonPath = path.join(this.pluginPath, item.name, 'package.json');
if (fs.existsSync(packageJsonPath)) {
try {
const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
if (pkg.name === pluginId) {
return item.name;
}
} catch (e) { }
}
// 如果目录名就是 ID
if (item.name === pluginId) {
return item.name;
}
}
return null;
}
}

View File

@@ -0,0 +1,487 @@
import fs from 'fs';
import path from 'path';
import { ActionMap } from '@/napcat-onebot/action';
import { NapCatCore } from 'napcat-core';
import { NapCatOneBot11Adapter, OB11Message } from '@/napcat-onebot/index';
import { OB11EmitEventContent, OB11NetworkReloadType } from '@/napcat-onebot/network/index';
import { IOB11NetworkAdapter } from '@/napcat-onebot/network/adapter';
import { PluginConfig } from '@/napcat-onebot/config/config';
import { NapCatConfig } from './config';
import { PluginLoader } from './loader';
import {
PluginEntry,
PluginLogger,
PluginStatusConfig,
NapCatPluginContext,
IPluginManager,
} from './types';
export class OB11PluginManager extends IOB11NetworkAdapter<PluginConfig> implements IPluginManager {
private readonly pluginPath: string;
private readonly configPath: string;
private readonly loader: PluginLoader;
/** 插件注册表: ID -> 插件条目 */
private plugins: Map<string, PluginEntry> = new Map();
declare config: PluginConfig;
public NapCatConfig = NapCatConfig;
override get isActive (): boolean {
return this.isEnable && this.getLoadedPlugins().length > 0;
}
constructor (
name: string,
core: NapCatCore,
obContext: NapCatOneBot11Adapter,
actions: ActionMap
) {
const config = {
name,
messagePostFormat: 'array',
reportSelfMessage: true,
enable: true,
debug: true,
};
super(name, config, core, obContext, actions);
this.pluginPath = this.core.context.pathWrapper.pluginPath;
this.configPath = path.join(this.core.context.pathWrapper.configPath, 'plugins.json');
this.loader = new PluginLoader(this.pluginPath, this.configPath, this.logger);
}
// ==================== 插件状态配置 ====================
public getPluginConfig (): PluginStatusConfig {
return this.loader.loadPluginStatusConfig();
}
private savePluginConfig (config: PluginStatusConfig): void {
this.loader.savePluginStatusConfig(config);
}
// ==================== 插件扫描与加载 ====================
/**
* 扫描并加载所有插件
*/
private async scanAndLoadPlugins (): Promise<void> {
// 扫描所有插件目录
const entries = await this.loader.scanPlugins();
// 清空现有注册表
this.plugins.clear();
// 注册所有插件条目
for (const entry of entries) {
this.plugins.set(entry.id, entry);
}
this.logger.log(`[PluginManager] Scanned ${this.plugins.size} plugins`);
// 加载启用的插件
for (const entry of this.plugins.values()) {
if (entry.enable && entry.runtime.status !== 'error') {
await this.loadPlugin(entry);
}
}
const loadedCount = this.getLoadedPlugins().length;
this.logger.log(`[PluginManager] Loaded ${loadedCount} plugins`);
}
/**
* 加载单个插件
*/
private async loadPlugin (entry: PluginEntry): Promise<boolean> {
if (entry.loaded) {
return true;
}
if (entry.runtime.status === 'error') {
return false;
}
// 加载模块
const module = await this.loader.loadPluginModule(entry);
if (!module) {
return false;
}
// 创建上下文
const context = this.createPluginContext(entry);
// 初始化插件
try {
await module.plugin_init(context);
entry.loaded = true;
entry.runtime = {
status: 'loaded',
module,
context,
};
this.logger.log(`[PluginManager] Initialized plugin: ${entry.id}${entry.version ? ` v${entry.version}` : ''}`);
return true;
} catch (error: any) {
entry.loaded = false;
entry.runtime = {
status: 'error',
error: error.message || 'Initialization failed',
};
this.logger.logError(`[PluginManager] Error initializing plugin ${entry.id}:`, error);
return false;
}
}
/**
* 卸载单个插件
*/
private async unloadPlugin (entry: PluginEntry): Promise<void> {
if (!entry.loaded || entry.runtime.status !== 'loaded') {
return;
}
const { module, context } = entry.runtime;
// 调用清理方法
if (module && context && typeof module.plugin_cleanup === 'function') {
try {
await module.plugin_cleanup(context);
this.logger.log(`[PluginManager] Cleaned up plugin: ${entry.id}`);
} catch (error) {
this.logger.logError(`[PluginManager] Error cleaning up plugin ${entry.id}:`, error);
}
}
// 重置状态
entry.loaded = false;
entry.runtime = {
status: 'unloaded',
};
this.logger.log(`[PluginManager] Unloaded plugin: ${entry.id}`);
}
/**
* 创建插件上下文
*/
private createPluginContext (entry: PluginEntry): NapCatPluginContext {
const dataPath = path.join(entry.pluginPath, 'data');
const configPath = path.join(dataPath, 'config.json');
// 创建插件专用日志器
const pluginPrefix = `[Plugin: ${entry.id}]`;
const coreLogger = this.logger;
const pluginLogger: PluginLogger = {
log: (...args: any[]) => coreLogger.log(pluginPrefix, ...args),
debug: (...args: any[]) => coreLogger.logDebug(pluginPrefix, ...args),
info: (...args: any[]) => coreLogger.log(pluginPrefix, ...args),
warn: (...args: any[]) => coreLogger.logWarn(pluginPrefix, ...args),
error: (...args: any[]) => coreLogger.logError(pluginPrefix, ...args),
};
return {
core: this.core,
oneBot: this.obContext,
actions: this.actions,
pluginName: entry.id,
pluginPath: entry.pluginPath,
dataPath,
configPath,
NapCatConfig,
adapterName: this.name,
pluginManager: this,
logger: pluginLogger,
};
}
// ==================== 公共 API ====================
/**
* 获取插件目录路径
*/
public getPluginPath (): string {
return this.pluginPath;
}
/**
* 获取所有插件条目
*/
public getAllPlugins (): PluginEntry[] {
return Array.from(this.plugins.values());
}
/**
* 获取已加载的插件列表
*/
public getLoadedPlugins (): PluginEntry[] {
return Array.from(this.plugins.values()).filter(p => p.loaded);
}
/**
* 通过 ID 获取插件信息
*/
public getPluginInfo (pluginId: string): PluginEntry | undefined {
return this.plugins.get(pluginId);
}
/**
* 设置插件状态(启用/禁用)
*/
public async setPluginStatus (pluginId: string, enable: boolean): Promise<void> {
const config = this.getPluginConfig();
config[pluginId] = enable;
this.savePluginConfig(config);
const entry = this.plugins.get(pluginId);
if (entry) {
entry.enable = enable;
if (enable && !entry.loaded) {
// 启用插件
await this.loadPlugin(entry);
} else if (!enable && entry.loaded) {
// 禁用插件
await this.unloadPlugin(entry);
}
}
}
/**
* 通过 ID 加载插件
*/
public async loadPluginById (pluginId: string): Promise<boolean> {
let entry = this.plugins.get(pluginId);
if (!entry) {
// 尝试查找并扫描
const dirname = this.loader.findPluginDirById(pluginId);
if (!dirname) {
this.logger.logWarn(`[PluginManager] Plugin ${pluginId} not found in filesystem`);
return false;
}
const newEntry = this.loader.rescanPlugin(dirname);
if (!newEntry) {
return false;
}
this.plugins.set(newEntry.id, newEntry);
entry = newEntry;
}
return await this.loadPlugin(entry);
}
/**
* 卸载插件(仅从内存卸载)
*/
public async unregisterPlugin (pluginId: string): Promise<void> {
const entry = this.plugins.get(pluginId);
if (entry) {
await this.unloadPlugin(entry);
}
}
/**
* 卸载并删除插件
*/
public async uninstallPlugin (pluginId: string, cleanData: boolean = false): Promise<void> {
const entry = this.plugins.get(pluginId);
if (!entry) {
throw new Error(`Plugin ${pluginId} not found`);
}
const pluginPath = entry.pluginPath;
const dataPath = path.join(pluginPath, 'data');
// 先卸载插件
await this.unloadPlugin(entry);
// 从注册表移除
this.plugins.delete(pluginId);
// 删除插件目录
if (fs.existsSync(pluginPath)) {
fs.rmSync(pluginPath, { recursive: true, force: true });
}
// 清理数据
if (cleanData && fs.existsSync(dataPath)) {
fs.rmSync(dataPath, { recursive: true, force: true });
}
}
/**
* 重载指定插件
*/
public async reloadPlugin (pluginId: string): Promise<boolean> {
const entry = this.plugins.get(pluginId);
if (!entry) {
this.logger.logWarn(`[PluginManager] Plugin ${pluginId} not found`);
return false;
}
try {
// 卸载插件
await this.unloadPlugin(entry);
// 重新扫描
const newEntry = this.loader.rescanPlugin(entry.fileId);
if (!newEntry) {
return false;
}
// 更新注册表
this.plugins.set(newEntry.id, newEntry);
// 重新加载
if (newEntry.enable) {
await this.loadPlugin(newEntry);
}
this.logger.log(`[PluginManager] Plugin ${pluginId} reloaded successfully`);
return true;
} catch (error) {
this.logger.logError(`[PluginManager] Error reloading plugin ${pluginId}:`, error);
return false;
}
}
/**
* 加载目录插件(用于新安装的插件)
*/
public async loadDirectoryPlugin (dirname: string): Promise<void> {
const entry = this.loader.rescanPlugin(dirname);
if (!entry) {
return;
}
// 检查是否已存在
if (this.plugins.has(entry.id)) {
this.logger.logWarn(`[PluginManager] Plugin ${entry.id} already exists`);
return;
}
this.plugins.set(entry.id, entry);
if (entry.enable && entry.runtime.status !== 'error') {
await this.loadPlugin(entry);
}
}
/**
* 获取插件数据目录路径
*/
public getPluginDataPath (pluginId: string): string {
const entry = this.plugins.get(pluginId);
if (!entry) {
throw new Error(`Plugin ${pluginId} not found`);
}
return path.join(entry.pluginPath, 'data');
}
/**
* 获取插件配置文件路径
*/
public getPluginConfigPath (pluginId: string): string {
return path.join(this.getPluginDataPath(pluginId), 'config.json');
}
// ==================== 事件处理 ====================
async onEvent<T extends OB11EmitEventContent> (event: T): Promise<void> {
if (!this.isEnable) {
return;
}
try {
await Promise.allSettled(
this.getLoadedPlugins().map((entry) =>
this.callPluginEventHandler(entry, event)
)
);
} catch (error) {
this.logger.logError('[PluginManager] Error handling event:', error);
}
}
/**
* 调用插件的事件处理方法
*/
private async callPluginEventHandler (
entry: PluginEntry,
event: OB11EmitEventContent
): Promise<void> {
if (entry.runtime.status !== 'loaded' || !entry.runtime.module || !entry.runtime.context) {
return;
}
const { module, context } = entry.runtime;
try {
// 优先使用 plugin_onevent 方法
if (typeof module.plugin_onevent === 'function') {
await module.plugin_onevent(context, event);
}
// 如果是消息事件并且插件有 plugin_onmessage 方法,也调用
if (
(event as any).message_type &&
typeof module.plugin_onmessage === 'function'
) {
await module.plugin_onmessage(context, event as OB11Message);
}
} catch (error) {
this.logger.logError(`[PluginManager] Error calling plugin ${entry.id} event handler:`, error);
}
}
// ==================== 生命周期 ====================
async open (): Promise<void> {
if (this.isEnable) {
return;
}
this.logger.log('[PluginManager] Opening plugin manager...');
this.isEnable = true;
// 扫描并加载所有插件
await this.scanAndLoadPlugins();
this.logger.log(`[PluginManager] Plugin manager opened with ${this.getLoadedPlugins().length} plugins loaded`);
}
async close (): Promise<void> {
if (!this.isEnable) {
return;
}
this.logger.log('[PluginManager] Closing plugin manager...');
this.isEnable = false;
// 卸载所有已加载的插件
for (const entry of this.plugins.values()) {
if (entry.loaded) {
await this.unloadPlugin(entry);
}
}
this.logger.log('[PluginManager] Plugin manager closed');
}
async reload (): Promise<OB11NetworkReloadType> {
this.logger.log('[PluginManager] Reloading plugin manager...');
// 先关闭然后重新打开
await this.close();
await this.open();
this.logger.log('[PluginManager] Plugin manager reloaded');
return OB11NetworkReloadType.Normal;
}
}

View File

@@ -0,0 +1,222 @@
import { NapCatCore } from 'napcat-core';
import { NapCatOneBot11Adapter, OB11Message } from '@/napcat-onebot/index';
import { ActionMap } from '@/napcat-onebot/action';
import { OB11EmitEventContent } from '@/napcat-onebot/network/index';
// ==================== 插件包信息 ====================
export interface PluginPackageJson {
name?: string;
plugin?: string;
version?: string;
main?: string;
description?: string;
author?: string;
}
// ==================== 插件配置 Schema ====================
export interface PluginConfigItem {
key: string;
type: 'string' | 'number' | 'boolean' | 'select' | 'multi-select' | 'html' | 'text';
label: string;
description?: string;
default?: unknown;
options?: { label: string; value: string | number; }[];
placeholder?: string;
/** 标记此字段为响应式:值变化时触发 schema 刷新 */
reactive?: boolean;
/** 是否隐藏此字段 */
hidden?: boolean;
}
export type PluginConfigSchema = PluginConfigItem[];
// ==================== NapCatConfig 静态接口 ====================
/** NapCatConfig 类的静态方法接口(用于 typeof NapCatConfig */
export interface INapCatConfigStatic {
text (key: string, label: string, defaultValue?: string, description?: string, reactive?: boolean): PluginConfigItem;
number (key: string, label: string, defaultValue?: number, description?: string, reactive?: boolean): PluginConfigItem;
boolean (key: string, label: string, defaultValue?: boolean, description?: string, reactive?: boolean): PluginConfigItem;
select (key: string, label: string, options: { label: string; value: string | number; }[], defaultValue?: string | number, description?: string, reactive?: boolean): PluginConfigItem;
multiSelect (key: string, label: string, options: { label: string; value: string | number; }[], defaultValue?: (string | number)[], description?: string, reactive?: boolean): PluginConfigItem;
html (content: string): PluginConfigItem;
plainText (content: string): PluginConfigItem;
combine (...items: PluginConfigItem[]): PluginConfigSchema;
}
/** NapCatConfig 类型(包含静态方法) */
export type NapCatConfigClass = INapCatConfigStatic;
// ==================== 插件管理器接口 ====================
/** 插件管理器公共接口 */
export interface IPluginManager {
readonly config: unknown;
getPluginPath (): string;
getPluginConfig (): PluginStatusConfig;
getAllPlugins (): PluginEntry[];
getLoadedPlugins (): PluginEntry[];
getPluginInfo (pluginId: string): PluginEntry | undefined;
setPluginStatus (pluginId: string, enable: boolean): Promise<void>;
loadPluginById (pluginId: string): Promise<boolean>;
unregisterPlugin (pluginId: string): Promise<void>;
uninstallPlugin (pluginId: string, cleanData?: boolean): Promise<void>;
reloadPlugin (pluginId: string): Promise<boolean>;
loadDirectoryPlugin (dirname: string): Promise<void>;
getPluginDataPath (pluginId: string): string;
getPluginConfigPath (pluginId: string): string;
}
// ==================== 插件配置 UI 控制器 ====================
/** 插件配置 UI 控制器 - 用于动态控制配置界面 */
export interface PluginConfigUIController {
/** 更新整个 schema */
updateSchema: (schema: PluginConfigSchema) => void;
/** 更新单个字段 */
updateField: (key: string, field: Partial<PluginConfigItem>) => void;
/** 移除字段 */
removeField: (key: string) => void;
/** 添加字段 */
addField: (field: PluginConfigItem, afterKey?: string) => void;
/** 显示字段 */
showField: (key: string) => void;
/** 隐藏字段 */
hideField: (key: string) => void;
/** 获取当前配置值 */
getCurrentConfig: () => Record<string, unknown>;
}
// ==================== 插件日志接口 ====================
/**
* 插件日志接口 - 简化的日志 API
*/
export interface PluginLogger {
/** 普通日志 */
log (...args: unknown[]): void;
/** 调试日志 */
debug (...args: unknown[]): void;
/** 信息日志 */
info (...args: unknown[]): void;
/** 警告日志 */
warn (...args: unknown[]): void;
/** 错误日志 */
error (...args: unknown[]): void;
}
// ==================== 插件上下文 ====================
export interface NapCatPluginContext {
core: NapCatCore;
oneBot: NapCatOneBot11Adapter;
actions: ActionMap;
pluginName: string;
pluginPath: string;
configPath: string;
dataPath: string;
/** NapCatConfig 配置构建器 */
NapCatConfig: NapCatConfigClass;
adapterName: string;
/** 插件管理器实例 */
pluginManager: IPluginManager;
/** 插件日志器 - 自动添加插件名称前缀 */
logger: PluginLogger;
}
// ==================== 插件模块接口 ====================
export interface PluginModule<T extends OB11EmitEventContent = OB11EmitEventContent> {
plugin_init: (ctx: NapCatPluginContext) => void | Promise<void>;
plugin_onmessage?: (
ctx: NapCatPluginContext,
event: OB11Message,
) => void | Promise<void>;
plugin_onevent?: (
ctx: NapCatPluginContext,
event: T,
) => void | Promise<void>;
plugin_cleanup?: (
ctx: NapCatPluginContext
) => void | Promise<void>;
plugin_config_schema?: PluginConfigSchema;
plugin_config_ui?: PluginConfigSchema;
plugin_get_config?: (ctx: NapCatPluginContext) => unknown | Promise<unknown>;
plugin_set_config?: (ctx: NapCatPluginContext, config: unknown) => void | Promise<void>;
/**
* 配置界面控制器 - 当配置界面打开时调用
* 返回清理函数,在界面关闭时调用
*/
plugin_config_controller?: (
ctx: NapCatPluginContext,
ui: PluginConfigUIController,
initialConfig: Record<string, unknown>
) => void | (() => void) | Promise<void | (() => void)>;
/**
* 响应式字段变化回调 - 当标记为 reactive 的字段值变化时调用
*/
plugin_on_config_change?: (
ctx: NapCatPluginContext,
ui: PluginConfigUIController,
key: string,
value: unknown,
currentConfig: Record<string, unknown>
) => void | Promise<void>;
}
// ==================== 插件运行时状态 ====================
export type PluginRuntimeStatus = 'loaded' | 'error' | 'unloaded';
export interface PluginRuntime {
/** 运行时状态 */
status: PluginRuntimeStatus;
/** 错误信息(当 status 为 'error' 时) */
error?: string;
/** 插件模块(当 status 为 'loaded' 时) */
module?: PluginModule;
/** 插件上下文(当 status 为 'loaded' 时) */
context?: NapCatPluginContext;
}
// ==================== 插件条目(统一管理所有插件) ====================
export interface PluginEntry {
// ===== 基础信息 =====
/** 插件 ID包名或目录名 */
id: string;
/** 文件系统目录名 */
fileId: string;
/** 显示名称 */
name?: string;
/** 版本号 */
version?: string;
/** 描述 */
description?: string;
/** 作者 */
author?: string;
/** 插件目录路径 */
pluginPath: string;
/** 入口文件路径 */
entryPath?: string;
/** package.json 内容 */
packageJson?: PluginPackageJson;
// ===== 状态 =====
/** 是否启用(用户配置) */
enable: boolean;
/** 运行时是否已加载 */
loaded: boolean;
// ===== 运行时 =====
/** 运行时信息 */
runtime: PluginRuntime;
}
// ==================== 插件状态配置(持久化) ====================
export interface PluginStatusConfig {
[key: string]: boolean; // key: pluginId, value: enabled
}